Author: vetinari
Date: Wed Aug 8 10:04:40 2007
New Revision: 768
Added:
contrib/vetinari/experimental/
contrib/vetinari/experimental/chunking
Modified:
contrib/vetinari/logging_apache
Log:
experimental plugin for the CHUNKING SMTP extension (RFC 1869, 1830/3030)
Added: contrib/vetinari/experimental/chunking
==============================================================================
--- (empty file)
+++ contrib/vetinari/experimental/chunking Wed Aug 8 10:04:40 2007
@@ -0,0 +1,261 @@
+#
+# chunking - plugin for the CHUNKING SMTP extension
+# (RFC 1869, 1830/3030)
+#
+
+=head1 NAME
+
+chunking - plugin for the CHUNKING SMTP extension (RFC 1830/3030)
+
+=head1 DESCRIPTION
+
+The B<chunking> plugin adds the SMTP CHUNKING extension from RFC 3030
+``SMTP Service Extensions for Transmission of Large and Binary MIME Messages''
+to qpsmtpd.
+
+=head1 CONFIG
+
+This plugin just accepts one parameter:
+
+=head2 binarymime
+
+This enables the "BODY=BINARYMIME" MAIL FROM: parameter. If not present, it
+will reject a
+ MAIL FROM:<[EMAIL PROTECTED]> BODY=BINARYMIME
+
+with a 555 error (see RFC 1869).
+
+Don't enable unless you're sure the backend MTA can handle this.
+
+=head1 NOTES
+
+This plugin has never been tested with a real remote MTA
+
+DON'T USE :P
+
+=cut
+
+use Qpsmtpd::DSN;
+use POSIX qw(strftime);
+
+sub register {
+ my ($self, $qp, @args) = @_;
+ if (@args > 2) {
+ $self->log(LOGERROR, "Bad parameters for the chunking plugin")
+ }
+ $self->{_binarymime} = 0;
+ if (lc $args[0] eq 'binarymime') {
+ $self->{_binarymime} = 1;
+ }
+}
+
+sub hook_ehlo {
+ my ($self, $transaction) = @_;
+ my $cap = $transaction->notes('capabilities');
+ $cap ||= [];
+ push @$cap, 'CHUNKING';
+ if ($self->{_binarymime}) {
+ push @$cap, 'BINARYMIME';
+ }
+ $transaction->notes('capabilities', $cap);
+ return(DECLINED);
+}
+
+sub hook_mail {
+ my ($self,$transaction,$sender,%params) = @_;
+ if (exists $params{'body'} && uc($params{'body'}) eq 'BINARYMIME') {
+ $transaction->notes('bdat_body_binarymime', 1);
+ unless ($self->{_binarymime}) {
+ my ($err) =
+ (Qpsmtpd::DSN->proto_syntax_error(
+ "BODY=BINARYMIME not supported AND not announced"))[1];
+ $self->qp->respond(555, $err);
+ return (DONE);
+ }
+ }
+ return (DECLINED);
+}
+
+sub hook_unrecognized_command {
+ my ($self, $transaction, $cmd, $size) = @_;
+ return (DECLINED) unless $cmd eq 'bdat';
+ my ($err, $last);
+
+ my $msg_size = $transaction->notes('bdat_size') || 0;
+
+ # DATA and BDAT commands cannot be used in the same transaction. If a
+ # DATA statement is issued after a BDAT for the current transaction, a
+ # 503 "Bad sequence of commands" MUST be issued. The state resulting
+ # from this error is indeterminate.
+ if ($transaction->notes('bdat_data')) {
+ ($err) =
+ (Qpsmtpd::DSN->proto_syntax_error("You cannot use BDAT and
DATA"))[1];
+ $self->qp->respond(503, $err);
+ return (DONE);
+ }
+
+ # Any BDAT command sent after the BDAT LAST is illegal and
+ # MUST be replied to with a 503 "Bad sequence of commands" reply code.
+ # The state resulting from this error is indeterminate. A RSET command
+ # MUST be sent to clear the transaction before continuing.
+ if ($transaction->notes('bdat_last')) {
+ ($err) =
+ (Qpsmtpd::DSN->proto_syntax_error("Bad sequence of commands"))[1];
+ $self->qp->respond(503, $err);
+ return (DONE);
+ }
+
+ ($err) =
+ (Qpsmtpd::DSN->proto_syntax_error("Syntax error in BDAT
parameter"))[1];
+
+ unless (defined $size || $size =~ /^\d+$/) {
+ $self->qp->respond(552, $err);
+ return (DONE);
+ }
+
+ if ($size =~ /^(\d+)\s*(LAST)?\s*$/) {
+ $size = $1;
+ $last = $2;
+ }
+
+ if (defined $last) {
+ if ($last =~ /^LAST$/i) { # RFC says LAST all upper, we don't care
+ $last = 1;
+ }
+ else {
+ $self->qp->respond(552, $err);
+ return (DONE);
+ }
+ }
+ else {
+ $last = 0;
+ ($last = 1) unless $size;
+ }
+
+ $transaction->notes('bdat_bdat', 1); # remember we've seen BDAT
+
+ # get a file to write the data chunks
+ my $file = $transaction->body_filename;
+ # ouch :o), don't do this abuse of internals at home kids :P
+ my $fh = $transaction->body_fh;
+ seek($fh, 0, 2)
+ or $self->log(LOGERROR, "failed to seek: $!"),
+ $self->qp->respond(452, "Temporary storage allocation error"),
+ return (DONE);
+
+ # we're at the end of the file, now read the chunk
+ my $rest = $size % 4096;
+ my $num = ($size-$rest)/4096;
+ my $i = 0;
+ my $buffer;
+ my $bytes;
+
+ while($i < $num) {
+ $bytes = read(STDIN, $buffer, 4096);
+ if ($bytes != 4096) {
+ $self->log(LOGERROR, "Failed to read: $!");
+ $self->qp->respond(452, "Error reading your data");
+ return (DONE);
+ }
+ print $fh $buffer
+ or $self->log(LOGERROR, "Failed to write: $!"),
+ $self->qp->respond(452, "Temporary storage allocation error"),
+ return (DONE);
+ ++$i;
+ }
+ $bytes = read(STDIN, $buffer, $rest);
+ if ($bytes != $rest) {
+ $self->log(LOGERROR, "Failed to read: $!");
+ $self->qp->respond(452, "Error reading your data");
+ return (DONE);
+ }
+ print $fh $buffer
+ or $self->log(LOGERROR, "Failed to write: $!"),
+ $self->qp->respond(452, "Temporary storage allocation error"),
+ return (DONE);
+ # ok, got the chunk on disk
+
+ # let's see if the mail is too big...
+ # ...we can't do this before reading the chunk, because the BDAT command
+ # requires us to read the chunk before responding
+ my $max = $self->qp->config('databytes');
+ if ($max) {
+ if (($msg_size + $size) > $max) {
+ $self->qp->respond(552, "Message too big!");
+ return(DONE);
+ }
+ }
+ $transaction->notes('bdat_size', $msg_size + $size);
+
+ if (!$last) { # get the next chunk
+ $self->qp->respond(250, "Ok, got $size octets");
+ return (DONE);
+ }
+ # else
+
+ # ... get the headers, run data_post & queue hooks
+ $transaction->notes('bdat_last', 1);
+ seek($fh, 0, 0)
+ or $self->log(LOGERROR, "Failed to seek: $!"),
+ $self->qp->respond(452, "Temporary storage allocation error"),
+ return (DONE);
+
+ $buffer = "";
+ while (<$fh>) {
+ last if /^\r?\n$/;
+ s/\r\n$/\n/;
+ $buffer .= $_;
+ }
+ # the body starts here...
+ $self->transaction->set_body_start();
+
+ my $header = Mail::Header->new(Modify => 0, MailFrom => "COERCE");
+ my @header = split /^/m, $buffer;
+ $header->extract([EMAIL PROTECTED]);
+ $self->transaction->header($header);
+
+ my $authheader = (defined $self->{_auth} and $self->{_auth} == OK)
+ ? "(smtp-auth username $self->{_auth_user}, "
+ ."mechanism $self->{_auth_mechanism})\n"
+ : "";
+
+ # no need for SMPT/ESMTP diff, we know we've just received via ESMTP (EHLO)
+ $header->add("Received",
+ "from ".$self->connection->remote_info
+ # can/should/must this be EHLO instead of HELO?
+ ." (HELO ".$self->connection->hello_host.")"
+ ." (". $self->connection->remote_ip. ")\n "
+ .$authheader
+ ." by ".$self->qp->config('me')." (qpsmtpd/".$self->qp->version.") "
+ ."with ESMTP". ($authheader ? "A" : "")."; " # ESMPTA: RFC 3848
+ .(strftime('%a, %d %b %Y %H:%M:%S %z', localtime)), 0);
+
+ # everything done for running data_post...
+ # this will call the spamassassin, virus scanner and queue plugins
+ # for us and do all the cleanup stuff
+ # ... in earlier versions (pre 0.40) of qpsmtpd we had to handle the
+ # return codes and do all the stuff
+ $self->qp->run_hooks("data_post");
+
+ # BDAT (0( LAST)?|$num LAST) is always the end of a "transaction"
+ # ... doesn't matter if it had done before
+ $self->qp->reset_transaction;
+ return (DONE);
+}
+
+sub hook_data {
+ my ($self, $transaction) = @_;
+ if ($transaction->notes('bdat_body_binarymime')
+ || $transaction->notes('bdat_bdat'))
+ {
+ my ($err) =
+ (Qpsmtpd::DSN->proto_syntax_error("Bad sequence of commands"))[1];
+ $self->qp->respond(503, $err);
+ return (DONE);
+ }
+ $transaction->notes('bdat_data', 1); # remeber we've seen DATA
+
+ return(DECLINED);
+}
+
+# vim: ts=4 sw=4 expandtab syn=perl
Modified: contrib/vetinari/logging_apache
==============================================================================
--- contrib/vetinari/logging_apache (original)
+++ contrib/vetinari/logging_apache Wed Aug 8 10:04:40 2007
@@ -48,7 +48,7 @@
# luckily apache uses the same log levels as qpsmtpd...
($trace = lc Qpsmtpd::Constants::log_level($trace)) =~ s/^log//;
- $trace = 'emerg'
+ $trace = 'emerg' # ... well, nearly...
if $trace eq 'radar';
my $log = $self->{_log};
@@ -76,7 +76,7 @@
=head1 INSTALL AND CONFIG
Place this plugin in the plugin/logging directory beneath the standard
-qpsmtpd installation. Edit the config/logging file and add a line like
+qpsmtpd installation. Edit the config/logging file and add a line like
this:
logging/apache