<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>.perltidyrc</filename>
    </added>
    <added>
      <filename>config.sample/invalid_resolvable_fromhost</filename>
    </added>
    <added>
      <filename>config.sample/size_threshold</filename>
    </added>
    <added>
      <filename>plugins/queue/exim-bsmtp</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -1,10 +1,20 @@
 0.40
 
-  Make the clamdscan plugin temporarily deny mail if if can't talk to clamd
-  (Filippo Carletti)
+0.31.1 - 2005/11/18
 
+  Add missing files to the distribution, oops... (Thanks Budi Ang!)
+  (exim plugin, tls plugin, various sample configuration files)
 
-0.31 -
+
+0.31 - 2005/11/16
+
+  STARTTLS support (see plugins/tls)
+
+  Added queue/exim-bsmtp plugin to spool accepted mail into an Exim
+  backend via BSMTP. (Devin Carraway)
+
+  New plugin inheritance system, see the bottom of README.plugins for
+  more information
 
   qpsmtpd-forkserver: --listen-address may now be given more than once, to
   request listening on multiple local addresses (Devin Carraway)
@@ -17,14 +27,41 @@
   postfix backend, which expects to have write permission to a fifo
   which usually belongs to group postdrop). (pjh)
 
+  qpsmtpd-forkserver: if -d or --detach is given on the commandline,
+  forkserver will detach from the controlling terminal and daemonize
+  itself (Devin Carraway)
+
+  replace some fun smtp comments with boring ones.
+
+  example patterns for badrcptto plugin - Gordon Rowell
+
+  Extend require_resolvable_fromhost to include a configurable list of
+  &quot;impossible&quot; addresses to combat spammer forging.  (Hanno Hecker)
+
+  Use qmail/control/smtpdgreeting if it exists, otherwise
+  show the original qpsmtpd greeting (with version information).
+
+  Apply slight variation on patch from Peter Holzer to allow specification of
+  an explicit $QPSMTPD_CONFIG variable to specify where the config lives,
+  overriding $QMAIL/control and /var/qmail/control if set.  The usual
+  &quot;last location with the file wins&quot; rule still applies.
+
+  Refactor Qpsmtpd::Address
+
   when disconncting with a temporary failure, return 421 rather than
   450 or 451. (Peter J. Holzer)
 
   The unrecognized_command hook now uses DENY_DISCONNECT return
   for disconnecting the user.
 
+  If the environment variable $QPSMTPD_CONFIG is set, qpsmtpd will look
+  for its config files in the directory given therein, in addition to (and
+  in preference to) other locations. (Peter J. Holzer)
+
   Updated documentation
 
+  Various minor cleanups
+
 
 0.30 - 2005/07/05
 </diff>
      <filename>Changes</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,8 @@
 Changes
 config.sample/badhelo
+config.sample/badrcptto_patterns
 config.sample/dnsbl_zones
+config.sample/invalid_resolvable_fromhost
 config.sample/IP
 config.sample/logging
 config.sample/loglevel
@@ -8,6 +10,7 @@ config.sample/plugins
 config.sample/relayclients
 config.sample/require_resolvable_fromhost
 config.sample/rhsbl_zones
+config.sample/size_threshold
 CREDITS
 lib/Apache/Qpsmtpd.pm
 lib/Qpsmtpd.pm
@@ -55,6 +58,7 @@ plugins/logging/adaptive
 plugins/logging/devnull
 plugins/logging/warn
 plugins/milter
+plugins/queue/exim-bsmtp
 plugins/queue/maildir
 plugins/queue/postfix-queue
 plugins/queue/qmail-queue
@@ -65,6 +69,7 @@ plugins/require_resolvable_fromhost
 plugins/rhsbl
 plugins/sender_permitted_from
 plugins/spamassassin
+plugins/tls
 plugins/virus/aveclient
 plugins/virus/bitdefender
 plugins/virus/check_for_hi_virus</diff>
      <filename>MANIFEST</filename>
    </modified>
    <modified>
      <diff>@@ -57,13 +57,9 @@ run the following command in the /home/smtpd/ directory.
 
    svn co http://svn.perl.org/qpsmtpd/trunk .
 
-Or if you want a specific release, use for example
+Beware that the trunk might be unstable and unsuitable for anything but development, so you might want to get a specific release, for example:
 
-   svn co http://svn.perl.org/qpsmtpd/tags/0.30 .
-
-In the branch L&lt;http://svn.perl.org/qpsmtpd/branches/high_perf/&gt; we
-have an experimental event based version of qpsmtpd that can handle
-thousands of simultaneous connections with very little overhead.
+   svn co http://svn.perl.org/qpsmtpd/tags/0.31.1 .
 
 chmod o+t ~smtpd/qpsmtpd/ (or whatever directory you installed qpsmtpd
 in) to make supervise start the log process.</diff>
      <filename>README</filename>
    </modified>
    <modified>
      <diff>@@ -10,13 +10,15 @@ pez (or pezmail)
 Near term roadmap
 =================
 
-0.31:
+0.32:
       - Bugfixes
       - add module requirements to the META.yml file
 
 0.40:
       - Add user configuration plugin
       - Add plugin API for checking if a local email address is valid
+      - use keyword &quot;ESMTPA&quot; in Received header in case of authentication to comply with RFC 3848.
+
 
 0.50:
       Include the popular check_delivery[1] functionality via the 0.30 API</diff>
      <filename>STATUS</filename>
    </modified>
    <modified>
      <diff>@@ -1,13 +1,13 @@
 package Qpsmtpd;
 use strict;
-use vars qw($VERSION $Logger $TraceLevel $Spool_dir);
+use vars qw($VERSION $Logger $TraceLevel $Spool_dir $Size_threshold);
 
 use Sys::Hostname;
 use Qpsmtpd::Constants;
 use Qpsmtpd::Transaction;
 use Qpsmtpd::Connection;
 
-$VERSION = &quot;0.31-dev&quot;;
+$VERSION = &quot;0.40-dev&quot;;
 
 sub version { $VERSION };
 
@@ -242,8 +242,6 @@ sub expand_inclusion_ {
 }
 
 
-#our $HOOKS;
-
 sub load_plugins {
   my $self = shift;
 
@@ -480,6 +478,29 @@ sub temp_dir {
   return $dirname;
 }
 
+sub size_threshold {
+  my $self = shift;
+  unless ( defined $Size_threshold ) {
+    $Size_threshold = $self-&gt;config('size_threshold') || 0;
+    $self-&gt;log(LOGNOTICE, &quot;size_threshold set to $Size_threshold&quot;);
+  }
+  return $Size_threshold;
+}
+
+sub auth_user {
+  my ($self, $user) = @_;
+  $user =~ s/[\r\n].*//s;
+  $self-&gt;{_auth_user} = $user if $user;    
+  return (defined $self-&gt;{_auth_user} ? $self-&gt;{_auth_user} : &quot;&quot; );
+}
+
+sub auth_mechanism {
+  my ($self, $mechanism) = @_;
+  $mechanism =~ s/[\r\n].*//s;
+  $self-&gt;{_auth_mechanism} = $mechanism if $mechanism;    
+  return (defined $self-&gt;{_auth_mechanism} ? $self-&gt;{_auth_mechanism} : &quot;&quot; );
+}
+  
 1;
 
 __END__</diff>
      <filename>lib/Qpsmtpd.pm</filename>
    </modified>
    <modified>
      <diff>@@ -1,16 +1,74 @@
+#!/usr/bin/perl -w
 package Qpsmtpd::Address;
 use strict;
 
+=head1 NAME
+
+Qpsmtpd::Address - Lightweight E-Mail address objects
+
+=head1 DESCRIPTION
+
+Based originally on cut and paste from Mail::Address and including 
+every jot and tittle from RFC-2821/2822 on what is a legal e-mail 
+address for use during the SMTP transaction.
+
+=head1 USAGE
+
+  my $rcpt = Qpsmtpd::Address-&gt;new('&lt;email.address@example.com&gt;');
+
+The objects created can be used as is, since they automatically 
+stringify to a standard form, and they have an overloaded comparison 
+for easy testing of values.
+
+=head1 METHODS
+
+=cut
+
+use overload (
+    '&quot;&quot;'   =&gt; \&amp;format,
+    'cmp'  =&gt; \&amp;_addr_cmp,
+);
+
+=head2 new()
+
+Can be called two ways:
+
+=over 4 
+
+=item * Qpsmtpd::Address-&gt;new('&lt;full_address@example.com&gt;')
+
+The normal mode of operation is to pass the entire contents of the 
+RCPT TO: command from the SMTP transaction.  The value will be fully 
+parsed via the L&lt;canonify&gt; method, using the full RFC 2821 rules.
+
+=item * Qpsmtpd::Address-&gt;new(&quot;user&quot;, &quot;host&quot;)
+
+If the caller has already split the address from the domain/host,
+this mode will not L&lt;canonify&gt; the input values.  This is not 
+recommended in cases of user-generated input for that reason.  This 
+can be used to generate Qpsmtpd::Address objects for accounts like 
+&quot;&lt;postmaster&gt;&quot; or indeed for the bounce address &quot;&lt;&gt;&quot;.
+
+=back
+
+The resulting objects can be stored in arrays or used in plugins to 
+test for equality (like in badmailfrom).
+
+=cut
+
 sub new {
-    my ($class, $address) = @_;
-    my $self = [ ];
-    if ($address =~ /^&lt;(.*)&gt;$/) {
-        $self-&gt;[0] = $1;
-      } else {
-        $self-&gt;[0] = $address;
+    my ($class, $user, $host) = @_;
+    my $self = {};
+    if ($user =~ /^&lt;(.*)&gt;$/ ) {
+	($user, $host) = $class-&gt;canonify($user)
     }
-    bless ($self, $class);
-    return $self;
+    elsif ( not defined $host ) {
+	my $address = $user;
+	($user, $host) = $address =~ m/(.*)(?:\@(.*))/;
+    }
+    $self-&gt;{_user} = $user;
+    $self-&gt;{_host} = $host;
+    return bless $self, $class;
 }
 
 # Definition of an address (&quot;path&quot;) from RFC 2821:
@@ -110,6 +168,15 @@ sub new {
 #
 # (We ignore all obs forms)
 
+=head2 canonify()
+
+Primarily an internal method, it is used only on the path portion of
+an e-mail message, as defined in RFC-2821 (this is the part inside the
+angle brackets and does not include the &quot;human readable&quot; portion of an
+address).  It returns a list of (local-part, domain).
+
+=cut
+
 sub canonify {
     my ($dummy, $path) = @_;
     my $atom = '[a-zA-Z0-9!#\$\%\&amp;\x27\*\+\x2D\/=\?\^_`{\|}~]+';
@@ -131,60 +198,131 @@ sub canonify {
     # empty path is ok
     return &quot;&quot; if $path eq &quot;&quot;;
 
-    # 
+    # bare postmaster is permissible, perl RFC-2821 (4.5.1)
+    return (&quot;postmaster&quot;, undef) if $path eq &quot;postmaster&quot;;
+    
     my ($localpart, $domainpart) = ($path =~ /^(.*)\@($domain)$/);
-    return undef unless defined $localpart;
+    return (undef) unless defined $localpart;
 
     if ($localpart =~ /^$atom(\.$atom)*/) {
         # simple case, we are done
-        return $path;
+        return ($localpart, $domainpart);
       }
     if ($localpart =~ /^&quot;(($qtext|\\$text)*)&quot;$/) {
         $localpart = $1;
         $localpart =~ s/\\($text)/$1/g;
-        return &quot;$localpart\@$domainpart&quot;;
+        return ($localpart, $domainpart);
       }
-    return undef;
+    return (undef);
 }
 
+=head2 parse()
+
+Retained as a compatibility method, it is completely equivalent
+to new() called with a single parameter.
 
+=cut
 
-sub parse {
-    my ($class, $line) = @_;
-    my $a = $class-&gt;canonify($line);
-    return ($class-&gt;new($a)) if (defined $a);
-    return undef;
+sub parse { # retain for compatibility only
+    return shift-&gt;new(shift);
 }
 
+=head2 address()
+
+Can be used to reset the value of an existing Q::A object, in which
+case it takes a parameter with or without the angle brackets.
+
+Returns the stringified representation of the address.  NOTE: does
+not escape any of the characters that need escaping, nor does it
+include the surrounding angle brackets.  For that purpose, see
+L&lt;format&gt;.
+
+=cut
+
 sub address {
     my ($self, $val) = @_;
-    my $oldval = $self-&gt;[0];
-    return $self-&gt;[0] = $val if (defined($val));
-    return $oldval;
+    if ( defined($val) ) {
+	$val = &quot;&lt;$val&gt;&quot; unless $val =~ /^&lt;.+&gt;$/;
+	my ($user, $host) = $self-&gt;canonify($val);
+	$self-&gt;{_user} = $user;
+	$self-&gt;{_host} = $host;
+    }
+    return ( defined $self-&gt;{_user} ?     $self-&gt;{_user} : '' )
+         . ( defined $self-&gt;{_host} ? '@'.$self-&gt;{_host} : '' );
 }
 
+=head2 format()
+
+Returns the canonical stringified representation of the address.  It
+does escape any characters requiring it (per RFC-2821/2822) and it
+does include the surrounding angle brackets.  It is also the default
+stringification operator, so the following are equivalent:
+
+  print $rcpt-&gt;format();
+  print $rcpt;
+
+=cut
+
 sub format {
     my ($self) = @_;
     my $qchar = '[^a-zA-Z0-9!#\$\%\&amp;\x27\*\+\x2D\/=\?\^_`{\|}~.]';
-    my $s = $self-&gt;[0];
-    return '&lt;&gt;' unless $s;
-    my ($user, $host) = $s =~ m/(.*)\@(.*)/;
-    if ($user =~ s/($qchar)/\\$1/g) {
-        return qq{&lt;&quot;$user&quot;\@$host&gt;};
+    return '&lt;&gt;' unless defined $self-&gt;{_user};
+    if ( ( my $user = $self-&gt;{_user}) =~ s/($qchar)/\\$1/g) {
+        return qq(&lt;&quot;$user&quot;)
+	. ( defined $self-&gt;{_host} ? '@'.$self-&gt;{_host} : '' ). &quot;&gt;&quot;;
       }
-    return &quot;&lt;$s&gt;&quot;;
+    return &quot;&lt;&quot;.$self-&gt;address().&quot;&gt;&quot;;
 }
 
+=head2 user()
+
+Returns the &quot;localpart&quot; of the address, per RFC-2821, or the portion
+before the '@' sign.
+
+=cut
+
 sub user {
     my ($self) = @_;
-    my ($user, $host) = $self-&gt;[0] =~ m/(.*)\@(.*)/;
-    return $user;
+    return $self-&gt;{_user};
 }
 
+=head2 host()
+
+Returns the &quot;domain&quot; part of the address, per RFC-2821, or the portion
+after the '@' sign.
+
+=cut
+
 sub host {
     my ($self) = @_;
-    my ($user, $host) = $self-&gt;[0] =~ m/(.*)\@(.*)/;
-    return $host;
+    return $self-&gt;{_host};
+}
+
+sub _addr_cmp {
+    require UNIVERSAL;
+    my ($left, $right, $swap) = @_;
+    my $class = ref($left);
+
+    unless ( UNIVERSAL::isa($right, $class) ) {
+	$right = $class-&gt;new($right);
+    }
+
+    #invert the address so we can sort by domain then user    
+    $left = lc($left-&gt;host.'='.$left-&gt;user);
+    $right = lc($right-&gt;host.'='.$right-&gt;user);
+
+    if ( $swap ) {
+	($right, $left) = ($left, $right);
+    }
+
+    return ($left cmp $right);
 }
 
+=head1 COPYRIGHT
+
+Copyright 2004-2005 Peter J. Holzer.  See the LICENSE file for more 
+information.
+
+=cut
+
 1;</diff>
      <filename>lib/Qpsmtpd/Address.pm</filename>
    </modified>
    <modified>
      <diff>@@ -226,19 +226,6 @@ sub e64
   return($res);
 }
 
-sub Qpsmtpd::SMTP::auth {
-    my ( $self, $arg, @stuff ) = @_;
-
-    #they AUTH'd once already
-    return $self-&gt;respond( 503, &quot;but you already said AUTH ...&quot; )
-      if ( defined $self-&gt;{_auth}
-        and $self-&gt;{_auth} == OK );
-    return $self-&gt;respond( 503, &quot;AUTH not defined for HELO&quot; )
-      if ( $self-&gt;connection-&gt;hello eq &quot;helo&quot; );
-
-    return $self-&gt;{_auth} = Qpsmtpd::Auth::SASL( $self, $arg, @stuff );
-}
-
 sub SASL {
 
     # $DB::single = 1;
@@ -326,9 +313,8 @@ sub SASL {
         $session-&gt;connection-&gt;relay_client(1);
         $session-&gt;log( LOGINFO, $msg );
 
-        $session-&gt;{_auth_user} = $user;
-        $session-&gt;{_auth_mechanism} = $mechanism;
-        s/[\r\n].*//s for ($session-&gt;{_auth_user}, $session-&gt;{_auth_mechanism}); 
+        $session-&gt;auth_user($user);
+        $session-&gt;auth_mechanism($mechanism);
 
         return OK;
     }</diff>
      <filename>lib/Qpsmtpd/Auth.pm</filename>
    </modified>
    <modified>
      <diff>@@ -37,9 +37,9 @@ sub _register {
   my $self = shift;
   my $qp = shift;
   local $self-&gt;{_qp} = $qp;
-  $self-&gt;init($qp, @_);
+  $self-&gt;init($qp, @_)     if $self-&gt;can('init');
   $self-&gt;_register_standard_hooks($qp, @_);
-  $self-&gt;register($qp, @_);
+  $self-&gt;register($qp, @_) if $self-&gt;can('register');
 }
 
 # Designed to be overloaded
@@ -73,6 +73,14 @@ sub spool_dir {
   shift-&gt;qp-&gt;spool_dir;
 }
 
+sub auth_user {
+    shift-&gt;qp-&gt;auth_user(@_);
+}
+
+sub auth_mechanism {
+    shift-&gt;qp-&gt;auth_mechanism(@_);
+}
+
 sub temp_file {
   my $self = shift;
   my $tempfile = $self-&gt;qp-&gt;temp_file;</diff>
      <filename>lib/Qpsmtpd/Plugin.pm</filename>
    </modified>
    <modified>
      <diff>@@ -15,6 +15,8 @@ use fields qw(
     hooks
     start_time
     _auth
+    _auth_user
+    _auth_mechanism
     _commands
     _config_cache
     _connection</diff>
      <filename>lib/Qpsmtpd/PollServer.pm</filename>
    </modified>
    <modified>
      <diff>@@ -196,7 +196,9 @@ sub ehlo_respond {
     $conn-&gt;hello_host($hello_host);
     $self-&gt;transaction;
 
-    my @capabilities = @{ $self-&gt;transaction-&gt;notes('capabilities') };
+    my @capabilities = $self-&gt;transaction-&gt;notes('capabilities')
+    			? @{ $self-&gt;transaction-&gt;notes('capabilities') }
+			: ();
 
     # Check for possible AUTH mechanisms
     my %auth_mechanisms;
@@ -227,6 +229,19 @@ HOOK: foreach my $hook ( keys %{$self-&gt;{hooks}} ) {
   }
 }
 
+sub auth {
+    my ( $self, $arg, @stuff ) = @_;
+    
+    #they AUTH'd once already
+    return $self-&gt;respond( 503, &quot;but you already said AUTH ...&quot; )
+      if ( defined $self-&gt;{_auth}
+        and $self-&gt;{_auth} == OK );
+    return $self-&gt;respond( 503, &quot;AUTH not defined for HELO&quot; )
+      if ( $self-&gt;connection-&gt;hello eq &quot;helo&quot; );
+
+    return $self-&gt;{_auth} = Qpsmtpd::Auth::SASL( $self, $arg, @stuff );
+}
+
 sub mail {
   my $self = shift;
   return $self-&gt;respond(501, &quot;syntax error in parameters&quot;) if !$_[0] or $_[0] !~ m/^from:/i;
@@ -365,7 +380,6 @@ sub rcpt_respond {
   return 0;
 }
 
-
 sub help {
   my $self = shift;
   $self-&gt;respond(214, </diff>
      <filename>lib/Qpsmtpd/SMTP.pm</filename>
    </modified>
    <modified>
      <diff>@@ -39,7 +39,7 @@ sub run {
     my $self = shift;
 
     # should be somewhere in Qpsmtpd.pm and not here...
-    $self-&gt;load_plugins;
+    $self-&gt;load_plugins unless $self-&gt;{hooks};
 
     my $rc = $self-&gt;start_conversation;
     return if $rc != DONE;</diff>
      <filename>lib/Qpsmtpd/TcpServer.pm</filename>
    </modified>
    <modified>
      <diff>@@ -15,9 +15,6 @@ sub start {
   my %args = @_;
   my $self = { _notes =&gt; { capabilities =&gt; [] }, _rcpt =&gt; [], started =&gt; time };
   bless ($self, $class);
-  my $sz = $self-&gt;config('memory_threshold');
-  $sz = 10_000 unless defined($sz);
-  $self-&gt;{_size_threshold} = $sz;
   return $self;
 }
 
@@ -91,13 +88,28 @@ sub body_current_pos {
     return $self-&gt;{_body_current_pos} || 0;
 }
 
-# TODO - should we create the file here if we're storing as an array?
 sub body_filename {
   my $self = shift;
-  return unless $self-&gt;{_body_file};
+  $self-&gt;body_spool() unless $self-&gt;{_filename};
+  $self-&gt;{_body_file}-&gt;flush(); # so contents won't be cached
   return $self-&gt;{_filename};
 }
 
+sub body_spool {
+  my $self = shift;
+  $self-&gt;log(LOGINFO, &quot;spooling message to disk&quot;);
+  $self-&gt;{_filename} = $self-&gt;temp_file();
+  $self-&gt;{_body_file} = IO::File-&gt;new($self-&gt;{_filename}, O_RDWR|O_CREAT, 0600)
+    or die &quot;Could not open file $self-&gt;{_filename} - $! &quot;; # . $self-&gt;{_body_file}-&gt;error;
+  if ($self-&gt;{_body_array}) {
+    foreach my $line (@{ $self-&gt;{_body_array} }) {
+      $self-&gt;{_body_file}-&gt;print($line) or die &quot;Cannot print to temp file: $!&quot;;
+    }
+    $self-&gt;{_body_start} = $self-&gt;{_header_size};
+  }
+  $self-&gt;{_body_array} = undef;
+}
+
 sub body_write {
   my $self = shift;
   my $data = shift;
@@ -125,19 +137,7 @@ sub body_write {
       $self-&gt;{_body_size} += length($1);
       ++$self-&gt;{_body_current_pos};
     }
-    if ($self-&gt;{_body_size} &gt;= $self-&gt;{_size_threshold}) {
-      #warn(&quot;spooling to disk\n&quot;);
-      $self-&gt;{_filename} = $self-&gt;temp_file();
-      $self-&gt;{_body_file} = IO::File-&gt;new($self-&gt;{_filename}, O_RDWR|O_CREAT, 0600)
-        or die &quot;Could not open file $self-&gt;{_filename} - $! &quot;; # . $self-&gt;{_body_file}-&gt;error;
-      if ($self-&gt;{_body_array}) {
-        foreach my $line (@{ $self-&gt;{_body_array} }) {
-          $self-&gt;{_body_file}-&gt;print($line) or die &quot;Cannot print to temp file: $!&quot;;
-        }
-        $self-&gt;{_body_start} = $self-&gt;{_header_size};
-      }
-      $self-&gt;{_body_array} = undef;
-    }
+    $self-&gt;body_spool if ( $self-&gt;{_body_size} &gt;= $self-&gt;size_threshold() );
   }
 }
 </diff>
      <filename>lib/Qpsmtpd/Transaction.pm</filename>
    </modified>
    <modified>
      <diff>@@ -2,13 +2,18 @@
 
 use Danga::DNS;
 
-sub register {
-  my ($self) = @_;
-  $self-&gt;register_hook(&quot;connect&quot;, &quot;connect_handler&quot;);
-  $self-&gt;register_hook(&quot;connect&quot;, &quot;pickup_handler&quot;);
+sub init {
+  my ($self, $qp, $denial ) = @_;
+  if ( defined $denial and $denial =~ /^disconnect$/i ) {
+    $self-&gt;{_dnsbl}-&gt;{DENY} = DENY_DISCONNECT;
+  }
+  else {
+    $self-&gt;{_dnsbl}-&gt;{DENY} = DENY;
+  }
+
 }
 
-sub connect_handler {
+sub hook_connect {
   my ($self, $transaction) = @_;
 
   my $remote_ip = $self-&gt;connection-&gt;remote_ip;
@@ -99,8 +104,9 @@ sub process_txt_result {
     # $qp-&gt;finish_continuation if $qp-&gt;input_sock-&gt;readable;
 }
 
-sub pickup_handler {
-  my ($self, $transaction) = @_;
+sub hook_rcpt {
+  my ($self, $transaction, $rcpt) = @_;
+  my $connection = $self-&gt;qp-&gt;connection;
 
   # RBLSMTPD being non-empty means it contains the failure message to return
   if (defined ($ENV{'RBLSMTPD'}) &amp;&amp; $ENV{'RBLSMTPD'} ne '') {
@@ -115,6 +121,14 @@ sub pickup_handler {
   return DECLINED;
 }
 
+sub hook_disconnect {
+  my ($self, $transaction) = @_;
+
+  $self-&gt;qp-&gt;connection-&gt;notes('dnsbl_sockets', undef);
+
+  return DECLINED;
+}
+
 1;
 
 =head1 NAME</diff>
      <filename>plugins/dnsbl</filename>
    </modified>
    <modified>
      <diff>@@ -1,22 +1,29 @@
 #!/usr/bin/perl
-
 use Danga::DNS;
 
-sub register {
-    my ($self) = @_;
-    $self-&gt;register_hook(&quot;mail&quot;, &quot;mail_handler&quot;);
-    $self-&gt;register_hook(&quot;rcpt&quot;, &quot;rcpt_handler&quot;);
+my %invalid = ();
+
+sub init {
+    my ($self, $qp) = @_;
+    foreach my $i ($qp-&gt;config(&quot;invalid_resolvable_fromhost&quot;)) {
+	$i =~ s/^\s*//;
+	$i =~ s/\s*$//;
+	if ($i =~ m#^((\d{1,3}\.){3}\d{1,3})/(\d\d?)#) {
+	    $invalid{$1} = $3;
+	}
+    }
 }
 
-sub mail_handler {
+sub hook_mail {
     my ($self, $transaction, $sender) = @_;
+    return DECLINED
+    	if ($self-&gt;qp-&gt;connection-&gt;notes('whitelistclient'));
     
     $self-&gt;transaction-&gt;notes('resolvable', 1);
     return DECLINED if $sender-&gt;format eq &quot;&lt;&gt;&quot;;
     return $self-&gt;check_dns($sender-&gt;host);
 }
 
-
 sub check_dns {
     my ($self, $host) = @_;
     
@@ -66,7 +73,7 @@ sub dns_result {
 }
 
 
-sub rcpt_handler {
+sub hook_rcpt {
     my ($self, $transaction) = @_;
     
     if (!$transaction-&gt;notes('resolvable')) {</diff>
      <filename>plugins/require_resolvable_fromhost</filename>
    </modified>
    <modified>
      <diff>@@ -2,14 +2,7 @@
 
 use Danga::DNS;
 
-sub register {
-  my ($self) = @_;
-
-  $self-&gt;register_hook('mail', 'mail_handler');
-  $self-&gt;register_hook('rcpt', 'rcpt_handler');
-}
-
-sub mail_handler {
+sub hook_mail {
   my ($self, $transaction, $sender) = @_;
 
   my %rhsbl_zones_map = ();
@@ -59,7 +52,7 @@ sub process_result {
     }
 }
 
-sub rcpt_handler {
+sub hook_rcpt {
   my ($self, $transaction, $rcpt) = @_;
 
   my $result = $transaction-&gt;notes('rhsbl');</diff>
      <filename>plugins/rhsbl</filename>
    </modified>
    <modified>
      <diff>@@ -39,6 +39,7 @@ sub init {
         SSL_server =&gt; 1
     ) or die &quot;Could not create SSL context: $!&quot;;
     
+    # now extract the password...
     $self-&gt;ssl_context($ssl_ctx);
     
     # Check for possible AUTH mechanisms
@@ -104,10 +105,18 @@ sub hook_unrecognized_command {
         
         my $conn = $self-&gt;connection;
         # Create a new connection object with subset of information collected thus far
-        my $newconn = Qpsmtpd::Connection-&gt;new();
-        for (qw(local_ip local_port remote_ip remote_port remote_host remote_info relay_client)) {
-            $newconn-&gt;$_($conn-&gt;$_());
-        }
+        my $newconn = Qpsmtpd::Connection-&gt;new(
+	    map { $_ =&gt; $conn-&gt;$_ }
+		qw(
+	           local_ip
+		   local_port
+		   remote_ip
+		   remote_port
+		   remote_host
+		   remote_info
+		   relay_client
+		),
+	);
         $self-&gt;qp-&gt;connection($newconn);
         $self-&gt;qp-&gt;reset_transaction;
         if ($self-&gt;qp-&gt;isa('Danga::Socket')) {</diff>
      <filename>plugins/tls</filename>
    </modified>
    <modified>
      <diff>@@ -118,7 +118,7 @@ sub hook_data_post {
     unless ( $content_type
         &amp;&amp; $content_type =~ m!\bmultipart/.*\bboundary=&quot;?([^&quot;]+)!i )
     {
-        $self-&gt;log( LOGERROR, &quot;non-multipart mail - skipping&quot; );
+        $self-&gt;log( LOGNOTICE, &quot;non-multipart mail - skipping&quot; );
         return DECLINED;
     }
 
@@ -153,7 +153,10 @@ sub hook_data_post {
         $clamd = Clamd-&gt;new();    # default unix domain socket
     }
 
-    return (DENYSOFT) unless $clamd-&gt;ping();
+    unless ( $clamd-&gt;ping() ) {
+	$self-&gt;log( LOGERROR, &quot;Cannot ping clamd server - did you provide the correct clamd port or socket?&quot; );
+	return DECLINED;
+    }
 
     if ( my %found = $clamd-&gt;scan($filename) ) {
         my $viruses = join( &quot;,&quot;, values(%found) );</diff>
      <filename>plugins/virus/clamdscan</filename>
    </modified>
    <modified>
      <diff>@@ -24,9 +24,6 @@ use Getopt::Long;
 
 $|++;
 
-# For debugging
-# $SIG{USR1} = sub { Carp::confess(&quot;USR1&quot;) };
-
 use Socket qw(SOMAXCONN IPPROTO_TCP SO_KEEPALIVE TCP_NODELAY SOL_SOCKET);
 
 $SIG{'PIPE'} = &quot;IGNORE&quot;;  # handled manually</diff>
      <filename>qpsmtpd</filename>
    </modified>
    <modified>
      <diff>@@ -39,7 +39,7 @@ usage: qpsmtpd-forkserver [ options ]
  -u, --user U              : run as a particular user (default 'smtpd')
  -m, --max-from-ip M       : limit connections from a single IP; default 5
      --pid-file P          : print main servers PID to file P
-     --detach              : detach from controlling terminal (daemonize)
+ -d, --detach              : detach from controlling terminal (daemonize)
 EOT
         exit 0;
 }
@@ -51,8 +51,8 @@ GetOptions('h|help' =&gt; \&amp;usage,
            'p|port=i' =&gt; \$PORT,
            'u|user=s' =&gt; \$USER,
            'pid-file=s' =&gt; \$PID_FILE,
-           'd|debug+' =&gt; \$DEBUG,
-           'detach' =&gt; \$DETACH,
+           'debug+' =&gt; \$DEBUG,
+           'd|detach' =&gt; \$DETACH,
 	  ) || &amp;usage;
 
 # detaint the commandline
@@ -172,6 +172,10 @@ if ($PID_FILE) {
   close PID;
 }
 
+# Populate class cached variables
+$qpsmtpd-&gt;spool_dir;
+$qpsmtpd-&gt;size_threshold;
+
 while (1) {
   REAPER();
   my $running = scalar keys %childstatus;
@@ -189,7 +193,6 @@ while (1) {
       # possible something condition...
       next;
     }
-    
     # Make this client blocking while we figure out if we actually want to
     # do something with it.
     IO::Handle::blocking($client, 1);
@@ -233,7 +236,17 @@ while (1) {
        ::log(LOGINFO, &quot;Connection Timed Out&quot;); 
        exit; };
   
-    ::log(LOGINFO, &quot;Accepted connection $running/$MAXCONN&quot;);
+    my $localsockaddr = getsockname($client);
+    my ($lport, $laddr) = sockaddr_in($localsockaddr);
+    $ENV{TCPLOCALIP} = inet_ntoa($laddr);
+    # my ($port, $iaddr) = sockaddr_in($hisaddr);
+    $ENV{TCPREMOTEIP} = inet_ntoa($iaddr);
+    $ENV{TCPREMOTEHOST} = gethostbyaddr($iaddr, AF_INET) || &quot;Unknown&quot;;
+  
+    # don't do this!
+    #$0 = &quot;qpsmtpd-forkserver: $ENV{TCPREMOTEIP} / $ENV{TCPREMOTEHOST}&quot;;
+  
+    ::log(LOGINFO, &quot;Accepted connection $running/$MAXCONN from $ENV{TCPREMOTEIP} / $ENV{TCPREMOTEHOST}&quot;);
     
     $::LineMode = 1;
     
@@ -245,11 +258,11 @@ while (1) {
     $qp-&gt;push_back_read(&quot;Connect\n&quot;);
     Qpsmtpd::PollServer-&gt;AddTimer(0.1, sub { });
     while (1) {
-        $qp-&gt;enable_read;
-        my $line = $qp-&gt;get_line;
-        last if !defined($line);
-        my $output = $qp-&gt;process_line($line);
-        $qp-&gt;write($output) if $output;
+	$qp-&gt;enable_read;
+	my $line = $qp-&gt;get_line;
+	last if !defined($line);
+	my $output = $qp-&gt;process_line($line);
+	$qp-&gt;write($output) if $output;
     }
     
     exit;                                   # child leaves</diff>
      <filename>qpsmtpd-forkserver</filename>
    </modified>
    <modified>
      <diff>@@ -2,7 +2,7 @@
 use strict;
 $^W = 1;
 
-use Test::More tests =&gt; 28;
+use Test::More tests =&gt; 29;
 
 BEGIN {
     use_ok('Qpsmtpd::Address');
@@ -16,6 +16,11 @@ $ao = Qpsmtpd::Address-&gt;parse($as);
 ok ($ao, &quot;parse $as&quot;);
 is ($ao-&gt;format, $as, &quot;format $as&quot;);
 
+$as = '&lt;postmaster&gt;';
+$ao = Qpsmtpd::Address-&gt;parse($as);
+ok ($ao, &quot;parse $as&quot;);
+is ($ao-&gt;format, $as, &quot;format $as&quot;);
+
 $as = '&lt;foo@example.com&gt;';
 $ao = Qpsmtpd::Address-&gt;parse($as);
 ok ($ao, &quot;parse $as&quot;);
@@ -38,21 +43,6 @@ $ao = Qpsmtpd::Address-&gt;parse($as);
 ok ($ao, &quot;parse $as&quot;);
 is ($ao-&gt;format, '&lt;&quot;foo\ bar&quot;@example.com&gt;', &quot;format $as&quot;);
 
-
-$as = 'foo@example.com';
-$ao = Qpsmtpd::Address-&gt;parse($as);
-is ($ao, undef, &quot;can't parse $as&quot;);
-
-$as = '&lt;@example.com&gt;';
-is (Qpsmtpd::Address-&gt;parse($as), undef, &quot;can't parse $as&quot;);
-
-$as = '&lt;@123&gt;';
-is (Qpsmtpd::Address-&gt;parse($as), undef, &quot;can't parse $as&quot;);
-
-$as = '&lt;user&gt;';
-is (Qpsmtpd::Address-&gt;parse($as), undef, &quot;can't parse $as&quot;);
-
-
 $as = 'foo@example.com';
 $ao = Qpsmtpd::Address-&gt;new($as);
 ok ($ao, &quot;new $as&quot;);
@@ -79,10 +69,35 @@ $as = '&lt;foo@foo.x.example.com&gt;';
 $ao = Qpsmtpd::Address-&gt;new($as);
 ok ($ao, &quot;new $as&quot;);
 is ($ao-&gt;format, $as, &quot;format $as&quot;);
+is (&quot;$ao&quot;, $as, &quot;overloaded stringify $as&quot;);
 
 $as = 'foo@foo.x.example.com';
 ok ($ao = Qpsmtpd::Address-&gt;parse(&quot;&lt;$as&gt;&quot;), &quot;parse &lt;$as&gt;&quot;);
 is ($ao &amp;&amp; $ao-&gt;address, $as, &quot;address $as&quot;);
-
-
+ok ($ao eq $as, &quot;overloaded 'cmp' operator&quot;);
+
+my @unsorted_list = map { Qpsmtpd::Address-&gt;new($_) }
+	qw(
+	    &quot;musa_ibrah@caramail.comandrea.luger&quot;@wifo.ac.at
+	    foo@example.com
+	    ask@perl.org
+	    foo@foo.x.example.com
+	    jpeacock@cpan.org
+	    test@example.com
+	);
+
+# NOTE that this is sorted by _host_ not by _domain_
+my @sorted_list = map { Qpsmtpd::Address-&gt;new($_) }
+	qw(
+	    jpeacock@cpan.org
+	    foo@example.com
+	    test@example.com
+	    foo@foo.x.example.com
+	    ask@perl.org
+	    &quot;musa_ibrah@caramail.comandrea.luger&quot;@wifo.ac.at
+	);
+
+my @test_list = sort @unsorted_list;
+
+is_deeply( \@test_list, \@sorted_list, &quot;sort via overloaded 'cmp' operator&quot;);
 </diff>
      <filename>t/qpsmtpd-address.t</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>8ac6157ee8303042e1c2fd15aeb22f93c7d0730d</id>
    </parent>
  </parents>
  <author>
    <name>John Peacock</name>
    <email>jpeacock@cpan.org</email>
  </author>
  <url>http://github.com/abh/qpsmtpd/commit/2535e772939f9f5f88aba016168d76e0e3abeac5</url>
  <id>2535e772939f9f5f88aba016168d76e0e3abeac5</id>
  <committed-date>2005-12-22T13:30:53-08:00</committed-date>
  <authored-date>2005-12-22T13:30:53-08:00</authored-date>
  <message>Merge branches/0.3x back to trunk.
Too many individual changes to document.  Trust me... ;-)

Lightly tested (i.e. it accepts and delivers mail with minimal plugins).

NOTES/LIMITATIONS: 
logging/adaptive currently eats some log messages.
auth_vpopmail_sql is currently broken (needs continuations?).
'make test' fails in dnsbl (no Test::Qpsmtpd::input_sock() method).


git-svn-id: https://svn.perl.org/qpsmtpd/trunk@588 958fd67b-6ff1-0310-b445-bb7760255be9</message>
  <tree>fc533d1024fdb216711ee1f7f162731d0b151d42</tree>
  <committer>
    <name>John Peacock</name>
    <email>jpeacock@cpan.org</email>
  </committer>
</commit>
