--On Saturday, November 27, 2004 12:24 AM -0800 Robert Spier
<[EMAIL PROTECTED]> wrote:

I can't easily extract the changes from that... well, I could, but it
would take longer, I think.  I can do  svn diff -r 22804, but there's
a lot of stuff that we've already got.

I have a 0.28 vendor branch in Subversion hidden away behind ACLs. (That's why Ask can easily generate the diff as he has full access.)

If you make a bunch of patches (against current CVS HEAD would be
great), I'll work on integrating them.

Here's a patch against 0.28. I'll try to remember to diff it against HEAD, but no promises. =)

This does include exe_filter, but as Ask said, "Why aren't we including that?"
I don't if anyone's approached the author of that plugin about bundling it.
(Before qpsmtpd, we lived by Russ Nelson's virusscan patch.)  There's also a
few other plugins I wrote to fit our config.  (We used the flame.org patches
with badheaders - so I ported over a plugin to read that.)

I did explain most of the patch to Ask, but can do so again if needed here on
list.  A lot of it is changing log levels or fixing bugs we saw.  (clamav has
some permission goofiness on FreeBSD - hence that chmod.)

Feel free to incorporate what you like and ignore what you don't.  And, note,
I'm *not* a Perl guy at all.  All I care about is keeping our SMTP server up.
=)

HTH.  And, thanks!  -- justin

P.S. Let me send this message with my subscribed email.  *smirk*
Index: plugins/check_badheaders
===================================================================
--- plugins/check_badheaders (.../vendor/qpsmtpd/current) (revision 0)
+++ plugins/check_badheaders (.../qpsmtpd) (revision 106696)
@@ -0,0 +1,26 @@
+sub register {
+ my ($self, $qp) = @_;
+ $self->register_hook("data_post", "check_badheaders");
+}
+
+sub check_badheaders {
+ my ($self, $transaction) = @_;
+
+ my @badheaders = $self->qp->config("badheaders") or return (DECLINED);
+ my @headers = split /\n/, $transaction->header->as_string;
+
+ for my $badline (@badheaders) {
+ #h R ^To: [EMAIL PROTECTED]
+ my @baditem = split / /, $badline, 3;
+ foreach my $header (@headers) {
+ if ($baditem[0] eq 'h' && $header =~ /$baditem[2]/ &&
+ $baditem[1] eq 'R') {
+ $self->log(1, "Rejected \'$header\'");
+ return (DENY,
+ "Sorry, but \'$header\' is not accepted here");
+ }
+ }
+ }
+
+ return (DECLINED);
+}
Index: plugins/check_badrcptto
===================================================================
--- plugins/check_badrcptto (.../vendor/qpsmtpd/current) (revision 106696)
+++ plugins/check_badrcptto (.../qpsmtpd) (revision 106696)
@@ -13,10 +13,14 @@
my $from = $recipient->user . '@' . $host;
for my $bad (@badrcptto) {
$bad =~ s/^\s*(\S+)/$1/;
- return (DENY, "mail to $bad not accepted here")
- if $bad eq $from;
- return (DENY, "mail to $bad not accepted here")
- if substr($bad,0,1) eq '@' && $bad eq "[EMAIL PROTECTED]";
+ if ($bad eq $from) {
+ $self->log(LOGINFO, "mail to $bad not accepted here");
+ return (DENY, "mail to $bad not accepted here");
+ }
+ if (substr($bad,0,1) eq '@' && $bad eq "@$host") {
+ $self->log(LOGINFO, "mail to $bad not accepted here");
+ return (DENY, "mail to $bad not accepted here");
+ }
}
return (DECLINED);
}
Index: plugins/sender_permitted_from
===================================================================
--- plugins/sender_permitted_from (.../vendor/qpsmtpd/current) (revision 106696)
+++ plugins/sender_permitted_from (.../qpsmtpd) (revision 106696)
@@ -85,14 +85,19 @@
my ($result, $smtp_comment, $comment) = $query->result2($rcpt->address);


  if ($result eq "error") {
-    return (DENYSOFT, "SPF error: $smtp_comment");
+    $self->log(LOGINFO, "SPF error: $smtp_comment");
+    # Don't return a DENY in this case.
+    #return (DENYSOFT, "SPF error: $smtp_comment");
+    return DECLINED;
  }

  if ($result eq "fail" and $self->{_args}{spf_deny}) {
+    $self->log(LOGINFO, "SPF forgery: $smtp_comment");
    return (DENY, "SPF forgery: $smtp_comment");
  }

  if ($result eq "softfail" and $self->{_args}{spf_deny} > 1) {
+    $self->log(LOGINFO, "SPF probable forgery: $smtp_comment");
    return (DENY, "SPF probable forgery: $smtp_comment");
  }

Index: plugins/queue/qmail-queue
===================================================================
--- plugins/queue/qmail-queue (.../vendor/qpsmtpd/current) (revision 106696)
+++ plugins/queue/qmail-queue (.../qpsmtpd) (revision 106696)
@@ -98,7 +98,7 @@
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: $!";


-    $self->log(LOGNOTICE, "Queuing to $queue_exec");
+    $self->log(LOGDEBUG, "Queuing to $queue_exec");

    my $rc = exec $queue_exec;

Index: plugins/count_unrecognized_commands
===================================================================
--- plugins/count_unrecognized_commands (.../vendor/qpsmtpd/current) (revision 106696)
+++ plugins/count_unrecognized_commands (.../qpsmtpd) (revision 106696)
@@ -34,6 +34,10 @@


  $self->log(LOGINFO, "Unrecognized command '$cmd'");

+ if (!$self->qp->connection->notes('unrec_cmd_count')) {
+ $self->qp->connection->notes('unrec_cmd_count', 0);
+ }
+
my $badcmdcount =
$self->qp->connection->notes('unrec_cmd_count',
$self->qp->connection->notes('unrec_cmd_count') + 1
Index: plugins/exe_filter
===================================================================
--- plugins/exe_filter (.../vendor/qpsmtpd/current) (revision 0)
+++ plugins/exe_filter (.../qpsmtpd) (revision 106696)
@@ -0,0 +1,233 @@
+=head1 NAME
+
+exe_filter
+
+=head1 DESCRIPTION
+
+exe_filter blocks executable (and other) attachments by matching the
+first body line of each MIME part in a message against a set of known
+signatures. If a match is found, the email is denied.
+
+Signatures are stored one per line in signature files in the qpsmtpd
+config directory. exe_filter currently supports 'signature_exe' and
+'signature_zip' files.
+
+=head1 CONFIG
+
+The following parameters can be passed to exe_filter, or set in a
+'exe_filter' config file.
+
+=over 4
+
+=item deny <suffixes>
+
+where <suffixes> is a comma-separated list of suffixes to deny e.g.
+
+ deny exe,zip
+
+A corresponding 'signature_<suffix>' file should exist for each supplied
+suffix.
+
+Default: 'deny exe'.
+
+=back
+
+The following parameter can be passed to exe_filter in config/plugins (but
+not set via a config file):
+
+=over 4
+
+=item per_recipient 1
+
+Allow per-recipient configs to be used (using the per_user_config plugin).
+Default: 0.
+
+=back
+
+
+=head1 BUGS AND LIMITATIONS
+
+exe_filter is a simple mime part filter - it does not unpack and scan
+archives for executables like a full-blown virus scanner. Likewise, zip
+filtering blocks *all* zip files, not just those that contain a virus. You
+should use a proper virus scanner if that's what you need.
+
+Because exe_filter is a post_data plugin, it cannot handle different
+configurations in per_recipient mode. This means that if you want to use
+per_recipient configurations, you should also enforce that only compatible
+recipients occur in a single mail (e.g. using a plugin like
+denysoft_multi_rcpt).
+
+
+=head1 AUTHOR
+
+Written by Gavin Carr <[EMAIL PROTECTED]>, inspired by Russ Nelson's
+viruscan patch to qmail-smtpd
+(http://www.qmail.org/qmail-smtpd-viruscan-1.2.patch).
+
+=cut
+
+my $VERSION = 0.02;
+my %DEFAULTS = ( deny => 'exe', per_recipient => 0 );
+
+sub register {
+ my ($self, $qp, %arg) = @_;
+ $self->{_config_defaults} = { %DEFAULTS, %arg };
+ $self->register_hook("rcpt", "setup_config") if $arg{per_recipient};
+ $self->register_hook("data_post", "filter_exe");
+}
+
+sub setup_config {
+ my ($self, $transaction, $rcpt) = @_;
+
+ # Setup only once
+ return DECLINED if $self->{_config};
+ return DECLINED
+ unless ref $self->{_config_defaults} eq 'HASH';
+
+ # Setup config from defaults and per_recipient exe_filter config
+ my @config = $self->qp->config('exe_filter', { rcpt => $rcpt });
+ $self->{_config} = {
+ %{$self->{_config_defaults}},
+ rcpt => $rcpt,
+ @config ? map { split /\s+/, $_, 2 } @config : ()
+ };
+ return DECLINED;
+}
+
+sub filter_exe {
+ my ($self, $transaction) = @_;
+
+ # Setup config parameters if not already done
+ my $config = $self->{_config};
+ unless ($config) {
+ my @config = $self->qp->config('exe_filter');
+ $config = {
+ %{$self->{_config_defaults}},
+ @config ? map { split /\s+/, $_, 2 } @config : ()
+ };
+ };
+ return DECLINED unless $config->{deny};
+
+ # Load signatures
+ my %sig = ();
+ my $config_arg = $config->{rcpt} ? { rcpt => $config->{rcpt} } : {};
+ for my $suffix (split /\s*,\s*/, $config->{deny}) {
+ my @sig = $self->qp->config("signatures_$suffix", $config_arg);
+ $self->log(LOGDEBUG, "warning - no signatures_$suffix loaded") unless @sig;
+ $sig{$suffix} = [EMAIL PROTECTED] if @sig;
+ }
+ return DECLINED unless keys %sig;
+
+ # Ignore non-multipart emails
+ my @boundary = ();
+ my $content_type = $transaction->header->get('Content-Type');
+ unless ($content_type && $content_type =~ m!\bmultipart/.*\bboundary="?([^"]+)!is) {
+ $self->log(LOGDEBUG, "non-multipart mail - skipping");
+ return DECLINED;
+ }
+ my $boundary = $1 || '';
+ $self->log(LOGDEBUG, "header boundary: $boundary");
+ push @boundary, $boundary;
+
+ # Make sure we read from the beginning;
+ $transaction->body_resetpos;
+
+ my $mail_header = undef;
+ my $mime_header = undef;
+ my $mime_lines = -1;
+ while ($_ = $transaction->body_getline) {
+ # Embedded mail headers
+ if (defined $mail_header) {
+ # Check for end of headers
+ if (m/^\s*$/) {
+ # Flatten continuation headers
+ $mail_header =~ s/\n\s+//g;
+ # Check for new Content-Type header
+ if (m/^Content-Type:.*\bmultipart.*\bboundary="?([^"]+)/ism) {
+ push @boundary, $1;
+ $self->log(LOGDEBUG, "new mail header content type: boundary $1");
+ }
+ $mail_header = undef;
+ }
+ else {
+ $mail_header .= $_;
+ }
+ }
+
+ # MIME Boundary
+ elsif (defined $boundary && m/^--\Q$boundary\E(--)?\s*$/) {
+ if ($1) {
+ $self->log(LOGDEBUG, "end boundary $boundary");
+ pop @boundary;
+ $boundary = $boundary[$#boundary];
+ }
+ else {
+ $self->log(LOGDEBUG, "found boundary $boundary");
+ # Turn mime_header on
+ $mime_header = '';
+ }
+ }
+
+ # MIME header
+ elsif (defined $mime_header) {
+ # Check for end of mime header
+ if (m/^\s*$/) {
+ # Flatten continuation headers
+ $mime_header =~ s/\n\s+//g;
+ # Check for attached messages
+ if ($mime_header =~ m!^Content-Type:.*message/rfc822!ism) {
+ # Turn mail_header on
+ $mail_header = '';
+ $mime_lines = undef;
+ $mime_header = undef;
+ }
+ else {
+ # Check for embedded multipart
+ if ($mime_header =~ m/^Content-type:.*\bmultipart.*\bboundary="?([^"]+)/ism) {
+ push @boundary, $1;
+ $boundary = $1;
+ $self->log(LOGDEBUG, "new boundary $boundary");
+ }
+ $mime_lines = 0;
+ $mime_header = undef;
+ }
+ }
+ # Build up current mime header
+ else {
+ $mime_header .= $_;
+ }
+ }
+
+ # MIME body
+ elsif (defined $mime_lines && $mime_lines >= 0) {
+ # Check first line of body against signatures
+ if ($_ && $mime_lines == 0) {
+ chomp $_;
+ $self->log(LOGDEBUG, "checking line1 $_");
+ for my $suffix (sort keys %sig) {
+ for my $sig (@{$sig{$suffix}}) {
+ next unless $sig;
+ if (m/^\Q$sig/) {
+ # Match - deny!
+ $self->log(LOGINFO, "denied matched $suffix sig '$sig': $_");
+ return (DENY, "\U$suffix\E attachments are not accepted here.");
+ }
+ }
+ }
+ }
+ $mime_lines++;
+ }
+
+ # Content outside MIME boundaries - okay at least at beginning of mail?
+ elsif (@boundary != 1) {
+ chomp;
+ $self->log(LOGDEBUG, "unhandled line: $_") if $_;
+ }
+ }
+
+ return (DECLINED);
+}
+
+# arch-tag: 3fc272f2-9d52-42d4-893b-032b529ec71d
+
Index: plugins/dnsbl
===================================================================
--- plugins/dnsbl (.../vendor/qpsmtpd/current) (revision 106696)
+++ plugins/dnsbl (.../qpsmtpd) (revision 106696)
@@ -13,10 +13,10 @@
# perform RBLSMTPD checks to mimic Dan Bernstein's rblsmtpd
if (defined($ENV{'RBLSMTPD'})) {
if ($ENV{'RBLSMTPD'} ne '') {
- $self->log(LOGINFO, "RBLSMTPD=\"$ENV{'RBLSMTPD'}\" for $remote_ip");
+ $self->log(LOGDEBUG, "RBLSMTPD=\"$ENV{'RBLSMTPD'}\" for $remote_ip");
return DECLINED;
} else {
- $self->log(LOGINFO, "RBLSMTPD set, but empty for $remote_ip");
+ $self->log(LOGDEBUG, "RBLSMTPD set, but empty for $remote_ip");
return DECLINED;
}
} else {
@@ -71,8 +71,8 @@


  $self->log(LOGDEBUG, "waiting for dnsbl dns");

-  # don't wait more than 8 seconds here
-  my @ready = $sel->can_read(8);
+  # don't wait more than 2 seconds here
+  my @ready = $sel->can_read(2);

$self->log(LOGDEBUG, "DONE waiting for dnsbl dns, got " , scalar @ready, " answers ...") ;
return '' unless @ready;
@@ -110,7 +110,7 @@
}
}
else {
- $self->log(LOGERROR, "$dnsbl query failed: ", $res->errorstring)
+ $self->log(LOGDEBUG, "$dnsbl query failed: ", $res->errorstring)
unless $res->errorstring eq "NXDOMAIN";
}


@@ -144,11 +144,14 @@
my $result = $ENV{'RBLSMTPD'};
my $remote_ip = $self->qp->connection->remote_ip;
$result =~ s/%IP%/$remote_ip/g;
- return (DENY, join(" ", $self->qp->config('dnsbl_rejectmsg'), $result));
+ return (DENYHARD, join(" ", $self->qp->config('dnsbl_rejectmsg'), $result));
}


  my $note = $self->process_sockets;
-  return (DENY, $note) if $note;
+  if ($note) {
+    $self->log(LOGINFO, $note);
+    return (DENY, $note);
+  }
  return DECLINED;
}

Index: plugins/clamav
===================================================================
--- plugins/clamav (.../vendor/qpsmtpd/current) (revision 106696)
+++ plugins/clamav (.../qpsmtpd) (revision 106696)
@@ -17,7 +17,7 @@
}
$self->log(LOGWARN, "WARNING: Ignoring additional arguments.") if (@args
1);
  } else {
-    $self->{_clamscan_loc} = "/usr/local/bin/clamscan";
+    $self->{_clamscan_loc} = "/usr/local/bin/clamdscan";
  }
}

@@ -25,6 +25,8 @@
  my ($self, $transaction) = @_;

  my ($temp_fh, $filename) = tempfile();
+  print $temp_fh "Received: ";
+  print $temp_fh $transaction->header->get("Received");
  print $temp_fh $transaction->header->as_string;
  print $temp_fh "\n";
  $transaction->body_resetpos;
@@ -32,9 +34,10 @@
    print $temp_fh $line;
  }
  seek($temp_fh, 0, 0);
+  chmod 0644, $filename;

# Now do the actual scanning!
- my $cmd = $self->{_clamscan_loc}." --stdout -i --max-recursion=50 --disable-summary $filename 2>&1";
+ my $cmd = $self->{_clamscan_loc}." --stdout --no-summary $filename 2>&1";
$self->log(LOGDEBUG, "Running: $cmd");
my $output = `$cmd`;


@@ -53,8 +56,8 @@
    return (DECLINED);
  }
  if ($result == 1) {
-    $self->log(LOGINFO, "Virus(es) found");
-    # return (DENY, "Virus Found: $output");
+    $self->log(LOGINFO, "Virus(es) found: $output");
+    return (DENY, "Virus Found: $output");
    $transaction->header->add('X-Virus-Found', 'Yes');
    $transaction->header->add('X-Virus-Details', $output);
  }
Index: plugins/spamassassin
===================================================================
--- plugins/spamassassin        (.../vendor/qpsmtpd/current)    (revision 
106696)
+++ plugins/spamassassin        (.../qpsmtpd)   (revision 106696)
@@ -86,7 +86,7 @@
sub check_spam {
  my ($self, $transaction) = @_;

-  $self->log(6, "check_spam");
+  $self->log(LOGDEBUG, "check_spam");
  return (DECLINED) if $transaction->body_size > 500_000;

  my $remote  = 'localhost';
@@ -139,16 +139,16 @@

  print SPAMD CRLF;
  shutdown(SPAMD, 1);
-  $self->log(6, "check_spam: finished sending to spamd");
+  $self->log(LOGDEBUG, "check_spam: finished sending to spamd");
  my $line0 = <SPAMD>; # get the first protocol lines out
  if ($line0) {
-    $self->log(6, "check_spam: spamd: $line0");
+    $self->log(LOGDEBUG, "check_spam: spamd: $line0");
    $transaction->header->add("X-Spam-Check-By", $self->qp->config('me'), 0);
  }

  my ($flag, $hits, $required);
  while (<SPAMD>) {
-    $self->log(6, "check_spam: spamd: $_");
+    $self->log(LOGDEBUG, "check_spam: spamd: $_");
    #warn "GOT FROM SPAMD1: $_";
    last unless m/\S/;
    if (m{Spam: (True|False) ; (-?\d+\.\d) / (-?\d+\.\d)}) {
@@ -159,13 +159,13 @@
  my $tests = <SPAMD>;
  $tests =~ s/\015//;  # hack for outlook
  $flag = $flag eq 'True' ? 'Yes' : 'No';
-  $self->log(6, "check_spam: finished reading from spamd");
+  $self->log(LOGDEBUG, "check_spam: finished reading from spamd");

-  $transaction->header->add('X-Spam-Flag', 'YES', 0) if ($flag eq 'Yes');
-  $transaction->header->add('X-Spam-Status',
+  $transaction->header->add('X-ASF-Spam-Flag', 'YES', 0) if ($flag eq 'Yes');
+  $transaction->header->add('X-ASF-Spam-Status',
                            "$flag, hits=$hits required=$required\n" .
                            "\ttests=$tests", 0);
-  $self->log(5, "check_spam: $flag, hits=$hits, required=$required, " .
+  $self->log(LOGDEBUG, "check_spam: $flag, hits=$hits, required=$required, " .
                             "tests=$tests");

  return (DECLINED);
@@ -174,14 +174,16 @@
sub check_spam_reject {
  my ($self, $transaction) = @_;

- $self->log(6, "check_spam_reject: reject_threshold=" . $self->{_args}->{reject_threshold});
+ $self->log(LOGDEBUG, "check_spam_reject: reject_threshold=" . $self->{_args}->{reject_threshold});
my $score = $self->get_spam_score($transaction) or return DECLINED;
- $self->log(6, "check_spam_reject: score=$score");
+ $self->log(LOGDEBUG, "check_spam_reject: score=$score");


-  return (DENY, "spam score exceeded threshold")
-    if $score >= $self->{_args}->{reject_threshold};
+  if ($score >= $self->{_args}->{reject_threshold}) {
+    $self->log(LOGINFO, "rejected with SA score: $score");
+    return (DENY, "spam score ($score) exceeded threshold");
+  }

-  $self->log(6, "check_spam_reject: passed");
+  $self->log(LOGDEBUG, "check_spam_reject: passed");
  return DECLINED;
}

@@ -200,7 +202,7 @@

sub get_spam_score {
my ($self, $transaction) = @_;
- my $status = $transaction->header->get('X-Spam-Status') or return;
+ my $status = $transaction->header->get('X-ASF-Spam-Status') or return;
my ($score) = ($status =~ m/hits=(-?\d+\.\d+)/)[0];
return $score;
}
Index: plugins/check_badmailfrom
===================================================================
--- plugins/check_badmailfrom (.../vendor/qpsmtpd/current) (revision 106696)
+++ plugins/check_badmailfrom (.../qpsmtpd) (revision 106696)
@@ -46,6 +46,9 @@
sub rcpt_handler {
my ($self, $transaction, $rcpt) = @_;
my $note = $transaction->notes('badmailfrom');
- return (DENY, $note) if $note;
+ if ($note) {
+ $self->log(LOGINFO, $note);
+ return (DENY, $note);
+ }
return (DECLINED);
}
Index: plugins/require_resolvable_fromhost
===================================================================
--- plugins/require_resolvable_fromhost (.../vendor/qpsmtpd/current) (revision 106696)
+++ plugins/require_resolvable_fromhost (.../qpsmtpd) (revision 106696)
@@ -7,8 +7,12 @@


sub mail_handler {
  my ($self, $transaction, $sender) = @_;
-
-  $sender->format ne "<>"
+
+  my $send_format = $sender->format;
+
+  # Need hack for "<>" (with quotes) appearing.
+  $send_format ne "<>"
+    and $send_format ne "\"<>\""
    and $self->qp->config("require_resolvable_fromhost")
    and !check_dns($sender->host)
    and return (DENYSOFT,
Index: plugins/check_earlytalker
===================================================================
--- plugins/check_earlytalker   (.../vendor/qpsmtpd/current)    (revision 
106696)
+++ plugins/check_earlytalker   (.../qpsmtpd)   (revision 106696)
@@ -34,9 +34,9 @@

  $in->add(\*STDIN) || return DECLINED;
  if ($in->can_read(1)) {
-    $self->log(LOGDEBUG, "remote host started talking before we said hello");
+    $self->log(LOGINFO, "remote host started talking before we said hello");
    return (DENYSOFT, "Don't be rude and talk before I say hello!");
  }
-  $self->log(LOGINFO,"remote host said nothing spontaneous, proceeding");
+  $self->log(LOGDEBUG,"remote host said nothing spontaneous, proceeding");
  return DECLINED;
}
Index: config/signatures_exe
===================================================================
--- config/signatures_exe       (.../vendor/qpsmtpd/current)    (revision 0)
+++ config/signatures_exe       (.../qpsmtpd)   (revision 106696)
@@ -0,0 +1,17 @@
+# Windows executables seen in active virii
+TVqQAAMAA
+TVpQAAIAA
+# Additional windows executable signatures not yet seen in virii
+TVpAALQAc
+TVpyAXkAX
+TVrmAU4AA
+TVrhARwAk
+TVoFAQUAA
+TVoAAAQAA
+TVoIARMAA
+TVouARsAA
+TVrQAT8AA
+# GIF file found in SoBig.F
+R0lGODlhaAA7APcAAP///+rp6puSp6GZrDUjUUc6Zn53mFJMdbGvvVtXh2xre8bF1x8cU4yLprOy
+# Seen in virus on [EMAIL PROTECTED] list
+TVoAAAEAAAACAAAA//8AAEAAAAAAAAAAQAAAAAAAAAC0TM0hAAAAAAAAAAAAAAAAAAAAAAAA
Index: config/exe_filter
===================================================================
--- config/exe_filter   (.../vendor/qpsmtpd/current)    (revision 0)
+++ config/exe_filter   (.../qpsmtpd)   (revision 106696)
@@ -0,0 +1 @@
+deny exe,zip,vbs
Index: config/signatures_zip
===================================================================
--- config/signatures_zip       (.../vendor/qpsmtpd/current)    (revision 0)
+++ config/signatures_zip       (.../qpsmtpd)   (revision 106696)
@@ -0,0 +1,6 @@
+# Generic ZIP file signatures?
+UEsDBBQAA
+UEsDBAoAAA
+# mydoom.A ZIP signatures
+# UEsDBAoAAAAAA.{6}zy5egAlgAAAJYAA
+# UEsDBAoAAAAAA.{6}KJx+eAFgAAABYAA
Index: config/loglevel
===================================================================
--- config/loglevel     (.../vendor/qpsmtpd/current)    (revision 106696)
+++ config/loglevel     (.../qpsmtpd)   (revision 106696)
@@ -8,4 +8,4 @@
# LOGALERT   = 2
# LOGEMERG   = 1
# LOGRADAR   = 0
-4
\ No newline at end of file
+7
Index: config/signatures_vbs
===================================================================
--- config/signatures_vbs       (.../vendor/qpsmtpd/current)    (revision 0)
+++ config/signatures_vbs       (.../qpsmtpd)   (revision 106696)
@@ -0,0 +1 @@
+ZGltIGZpbGVzeXMsIGZpbGV0eHQsIGdldG5hbWUsIHBhdGgsIHRleHRmaWxlLCBpDQp0ZXh0
Index: lib/Qpsmtpd.pm
===================================================================
--- lib/Qpsmtpd.pm      (.../vendor/qpsmtpd/current)    (revision 106696)
+++ lib/Qpsmtpd.pm      (.../qpsmtpd)   (revision 106696)
@@ -118,7 +118,7 @@

  my ($name) = ($0 =~ m!(.*?)/([^/]+)$!);
  my $dir = "$name/plugins";
-  $self->log(LOGNOTICE, "loading plugins from $dir");
+  $self->log(LOGDEBUG, "loading plugins from $dir");

  $self->_load_plugins($dir, @plugins);
}
@@ -128,7 +128,7 @@
  my ($dir, @plugins) = @_;

  for my $plugin (@plugins) {
-    $self->log(LOGINFO, "Loading $plugin");
+    $self->log(LOGDEBUG, "Loading $plugin");
    ($plugin, my @args) = split /\s+/, $plugin;

if (lc($plugin) eq '$include') {
@@ -216,7 +216,7 @@
if ($self->{_hooks}->{$hook}) {
my @r;
for my $code (@{$self->{_hooks}->{$hook}}) {
- $self->log(LOGINFO, "running plugin ", $code->{name});
+ $self->log(LOGDEBUG, "running plugin ", $code->{name});
eval { (@r) = $code->{code}->($self, $self->can('transaction') ? $self->transaction : {}, @_); };
$@ and $self->log(LOGCRIT, "FATAL PLUGIN ERROR: ", $@) and next;
!defined $r[0]
Index: lib/Qpsmtpd/TcpServer.pm
===================================================================
--- lib/Qpsmtpd/TcpServer.pm (.../vendor/qpsmtpd/current) (revision 106696)
+++ lib/Qpsmtpd/TcpServer.pm (.../qpsmtpd) (revision 106696)
@@ -51,7 +51,11 @@
sub read_input {
my $self = shift;


- my $timeout = $self->config('timeout');
+ my $timeout =
+ $self->config('timeoutsmtpd') # qmail smtpd control file
+ || $self->config('timeout') # qpsmtpd control file
+ || 1200; # default value
+
alarm $timeout;
while (<STDIN>) {
alarm 0;
Index: lib/Qpsmtpd/SMTP.pm
===================================================================
--- lib/Qpsmtpd/SMTP.pm (.../vendor/qpsmtpd/current) (revision 106696)
+++ lib/Qpsmtpd/SMTP.pm (.../qpsmtpd) (revision 106696)
@@ -205,9 +205,9 @@
}
else {
my $from_parameter = join " ", @_;
- $self->log(LOGINFO, "full from_parameter: $from_parameter");
+ $self->log(LOGDEBUG, "full from_parameter: $from_parameter");
my ($from) = ($from_parameter =~ m/^from:\s*(\S+)/i)[0];
- warn "$$ from email address : [$from]\n";
+ #warn "$$ from email address : [$from]\n";
if ($from eq "<>" or $from =~ m/\[undefined\]/) {
$from = Mail::Address->new("<>");
}
@@ -237,7 +237,7 @@
$self->disconnect;
}
else { # includes OK
- $self->log(LOGINFO, "getting mail from ".$from->format);
+ $self->log(LOGDEBUG, "getting mail from ".$from->format);
$self->respond(250, $from->format . ", sender OK - how exciting to get mail from you!");
$self->transaction->sender($from);
}
Index: qpsmtpd-forkserver
===================================================================
--- qpsmtpd-forkserver (.../vendor/qpsmtpd/current) (revision 106696)
+++ qpsmtpd-forkserver (.../qpsmtpd) (revision 106696)
@@ -1,4 +1,4 @@
-#!/usr/bin/perl -w
+#!/usr/bin/perl -Tw
# Copyright (c) 2001 Ask Bjoern Hansen. See the LICENSE file for details.
# The "command dispatch" system is taken from colobus - http://trainedmonkey.com/colobus/
#
@@ -11,22 +11,48 @@
use Qpsmtpd::Constants;
use IO::Socket;
use Socket;
+use Getopt::Long;
use POSIX qw(:sys_wait_h :errno_h :signal_h);
use strict;
$| = 1;


# Configuration
my $MAXCONN   = 15;                             # max simultaneous connections
-my $PORT      = 25;                            # port number
+my $PORT      = 2525;                          # port number
my $LOCALADDR = '0.0.0.0';              # ip address to bind to
my $USER      = 'smtpd';                # user to suid to
+my $MAXCONNIP = 5;              # max simultaneous connections from one IP

+sub usage {
+        print <<"EOT";
+usage: qpsmtpd-forkserver [ options ]
+ -l, --listen-address addr : listen on a specific address; default 0.0.0.0
+ -p, --port P              : listen on a specific port; default 25
+ -c, --limit-connections N : limit concurrent connections to N; default 15
+ -u, --user U              : run as a particular user (defualt 'smtpd')
+EOT
+        exit 0;
+}
+
+GetOptions('h|help' => \&usage,
+           'l|listen-address=s' => \$LOCALADDR,
+           'c|limit-connections=i' => \$MAXCONN,
+           'p|port=i' => \$PORT,
+           'u|user=s' => \$USER) || &usage;
+
+# detaint the commandline
+if ($PORT =~ /^(\d+)$/) { $PORT = $1 } else { &usage }
+if ($LOCALADDR =~ /^([\d\w\-.]+)$/) { $LOCALADDR = $1 } else { &usage }
+if ($USER =~ /^([\w\-]+)$/) { $USER = $1 } else { &usage }
+if ($MAXCONN =~ /^(\d+)$/) { $MAXCONN = $1 } else { &usage }
+
delete $ENV{ENV};
$ENV{PATH} = '/bin:/usr/bin:/var/qmail/bin';

my %childstatus = ();

sub REAPER {
+  $SIG{CHLD} = \&REAPER;
  while ( defined(my $chld = waitpid(-1, WNOHANG)) ){
    last unless $chld > 0;
    warn("$$ cleaning up after $chld\n");
@@ -34,7 +60,15 @@
  }
}

+sub HUNTSMAN {
+  $SIG{CHLD} = 'DEFAULT';
+  kill 'INT' => keys %childstatus;
+  exit(0);
+}
+
$SIG{CHLD} = \&REAPER;
+$SIG{INT} = \&HUNTSMAN;
+$SIG{TERM} = \&HUNTSMAN;

# establish SERVER socket, bind and listen.
my $server = IO::Socket::INET->new(LocalPort => $PORT,
@@ -42,10 +76,10 @@
                                   Proto     => 'tcp',
                                   Reuse     => 1,
                                   Listen    => SOMAXCONN )
-  or die "making socket: [EMAIL PROTECTED]";
+  or die "Creating TCP socket $LOCALADDR:$PORT: $!\n";
+::log(LOGINFO,"Listening on port $PORT");

# Drop priviledges
-my $user = 'mailfw';
my (undef, undef, $quid, $qgid) = getpwnam $USER or
      die "unable to determine uid/gid for $USER\n";
$) = "";
@@ -55,17 +89,23 @@
      die "unable to change uid: $!\n";
$> = $quid;

+::log(LOGINFO, 'Running as user '.
+       (getpwuid($>) || $>) .
+       ', group '.
+       (getgrgid($)) || $)));
+
# Load plugins here
my $plugin_loader = Qpsmtpd::TcpServer->new();
$plugin_loader->load_plugins;

-::log(LOGINFO,"Listening on port $PORT\n");

while (1) {
my $running = scalar keys %childstatus;
- while ($running >= $MAXCONN) {
- ::log(LOGINFO,"Too many connections: $running >= $MAXCONN. Waiting one second.");
- sleep(1) ;
+ my $delay = 0;
+ while ($running >= $MAXCONN) {
+ $delay += 0.5;
+ ::log(LOGINFO,"Too many connections: $running >= $MAXCONN. Waiting $delay second(s).");
+ sleep($delay);
$running = scalar keys %childstatus;
}
my $hisaddr = accept(my $client, $server);
@@ -73,10 +113,33 @@
# possible something condition...
next;
}
+ my ($port, $iaddr) = sockaddr_in($hisaddr);
+ if ($MAXCONNIP) {
+ my $num_conn = 1; # seed with current value
+
+ # If we for-loop directly over values %childstatus, a SIGCHLD
+ # can call REAPER and slip $rip out from under us. Causes
+ # "Use of freed value in iteration" under perl 5.8.4.
+ my @rip = values %childstatus;
+ foreach my $rip (@rip) {
+ ++$num_conn if (defined $rip && $rip eq $iaddr);
+ }
+
+ if ($num_conn > $MAXCONNIP) {
+ my $rem_ip = inet_ntoa($iaddr);
+ ::log(LOGINFO,"Too many connections from $rem_ip: "
+ ."$num_conn > $MAXCONNIP. Denying connection.");
+ $client->autoflush(1);
+ print $client "451 Sorry, too many connections from $rem_ip, try again later\r\n";
+ close $client;
+ next;
+ }
+ }
my $pid = fork;
if ($pid) {
# parent
- $childstatus{$pid} = 1; # add to table
+ $childstatus{$pid} = $iaddr; # add to table
+ # $childstatus{$pid} = 1; # add to table
$running++;
close($client);
next;
@@ -84,15 +147,21 @@
die "fork: $!" unless defined $pid; # failure
# otherwise child


+    # all children should have different seeds, to prevent conflicts
+    srand( time ^ ($$ + ($$ << 15)) );
+
    close($server);

-    $SIG{CHLD} = $SIG{HUP} = $SIG{PIPE} = $SIG{INT} =
-        $SIG{TERM} = $SIG{QUIT} = 'DEFAULT';
-
+    $SIG{$_} = 'DEFAULT' for keys %SIG;
+    $SIG{ALRM} = sub {
+       print $client "421 Connection Timed Out\n";
+       ::log(LOGINFO, "Connection Timed Out");
+       exit; };
+
    my $localsockaddr = getsockname($client);
    my ($lport, $laddr) = sockaddr_in($localsockaddr);
    $ENV{TCPLOCALIP} = inet_ntoa($laddr);
-    my ($port, $iaddr) = sockaddr_in($hisaddr);
+    # my ($port, $iaddr) = sockaddr_in($hisaddr);
    $ENV{TCPREMOTEIP} = inet_ntoa($iaddr);
    $ENV{TCPREMOTEHOST} = gethostbyaddr($iaddr, AF_INET) || "Unknown";

@@ -106,7 +175,13 @@
    POSIX::dup2(fileno($client), 1);

    my $qpsmtpd = Qpsmtpd::TcpServer->new();
-    $qpsmtpd->start_connection();
+    $qpsmtpd->start_connection
+      (
+       local_ip    => $ENV{TCPLOCALIP},
+       local_port  => $lport,
+       remote_ip   => $ENV{TCPREMOTEIP},
+       remote_port => $port,
+      );
    $qpsmtpd->run();

    exit;                                   # child leaves

Reply via email to