find . -name '*.pm' -exec perltidy -b {} \;
This commit is contained in:
parent
fd2c56fb36
commit
5b06929e95
@ -7,13 +7,13 @@ use warnings FATAL => 'all';
|
||||
use Apache2::ServerUtil ();
|
||||
use Apache2::Connection ();
|
||||
use Apache2::Const -compile => qw(OK MODE_GETLINE);
|
||||
use APR::Const -compile => qw(SO_NONBLOCK EOF SUCCESS);
|
||||
use APR::Error ();
|
||||
use APR::Brigade ();
|
||||
use APR::Bucket ();
|
||||
use APR::Socket ();
|
||||
use APR::Const -compile => qw(SO_NONBLOCK EOF SUCCESS);
|
||||
use APR::Error ();
|
||||
use APR::Brigade ();
|
||||
use APR::Bucket ();
|
||||
use APR::Socket ();
|
||||
use Apache2::Filter ();
|
||||
use ModPerl::Util ();
|
||||
use ModPerl::Util ();
|
||||
|
||||
our $VERSION = '0.02';
|
||||
|
||||
@ -25,11 +25,11 @@ sub handler {
|
||||
|
||||
my $qpsmtpd = Qpsmtpd::Apache->new();
|
||||
$qpsmtpd->start_connection(
|
||||
ip => $c->remote_ip,
|
||||
host => $c->remote_host,
|
||||
info => undef,
|
||||
conn => $c,
|
||||
);
|
||||
ip => $c->remote_ip,
|
||||
host => $c->remote_host,
|
||||
info => undef,
|
||||
conn => $c,
|
||||
);
|
||||
|
||||
$qpsmtpd->run($c);
|
||||
$qpsmtpd->run_hooks("post-connection");
|
||||
@ -46,20 +46,21 @@ use base qw(Qpsmtpd::SMTP);
|
||||
my %cdir_memo;
|
||||
|
||||
sub config_dir {
|
||||
my ($self, $config) = @_;
|
||||
if (exists $cdir_memo{$config}) {
|
||||
return $cdir_memo{$config};
|
||||
}
|
||||
my ($self, $config) = @_;
|
||||
if (exists $cdir_memo{$config}) {
|
||||
return $cdir_memo{$config};
|
||||
}
|
||||
|
||||
if (uc($ENV{QPSMTPD_CONFIG}) eq 'USE-VIRTUAL-DOMAINS') {
|
||||
my $cdir = $self->{conn}->base_server->dir_config("qpsmtpd.config_dir");
|
||||
$cdir =~ /^(.*)$/; # detaint
|
||||
my $configdir = $1 if -e "$1/$config";
|
||||
$cdir_memo{$config} = $configdir;
|
||||
} else {
|
||||
$cdir_memo{$config} = $self->SUPER::config_dir(@_);
|
||||
}
|
||||
return $cdir_memo{$config};
|
||||
if (uc($ENV{QPSMTPD_CONFIG}) eq 'USE-VIRTUAL-DOMAINS') {
|
||||
my $cdir = $self->{conn}->base_server->dir_config("qpsmtpd.config_dir");
|
||||
$cdir =~ /^(.*)$/; # detaint
|
||||
my $configdir = $1 if -e "$1/$config";
|
||||
$cdir_memo{$config} = $configdir;
|
||||
}
|
||||
else {
|
||||
$cdir_memo{$config} = $self->SUPER::config_dir(@_);
|
||||
}
|
||||
return $cdir_memo{$config};
|
||||
}
|
||||
|
||||
sub start_connection {
|
||||
@ -67,23 +68,26 @@ sub start_connection {
|
||||
my %opts = @_;
|
||||
|
||||
$self->{conn} = $opts{conn};
|
||||
$self->{conn}->client_socket->timeout_set($self->config('timeout') * 1_000_000);
|
||||
$self->{bb_in} = APR::Brigade->new($self->{conn}->pool, $self->{conn}->bucket_alloc);
|
||||
$self->{bb_out} = APR::Brigade->new($self->{conn}->pool, $self->{conn}->bucket_alloc);
|
||||
$self->{conn}
|
||||
->client_socket->timeout_set($self->config('timeout') * 1_000_000);
|
||||
$self->{bb_in} =
|
||||
APR::Brigade->new($self->{conn}->pool, $self->{conn}->bucket_alloc);
|
||||
$self->{bb_out} =
|
||||
APR::Brigade->new($self->{conn}->pool, $self->{conn}->bucket_alloc);
|
||||
|
||||
my $remote_host = $opts{host} || ( $opts{ip} ? "[$opts{ip}]" : "[noip!]");
|
||||
my $remote_host = $opts{host} || ($opts{ip} ? "[$opts{ip}]" : "[noip!]");
|
||||
my $remote_info = $opts{info} ? "$opts{info}\@$remote_host" : $remote_host;
|
||||
my $remote_ip = $opts{ip};
|
||||
|
||||
$self->log(LOGNOTICE, "Connection from $remote_info [$remote_ip]");
|
||||
|
||||
$self->SUPER::connection->start(
|
||||
remote_info => $remote_info,
|
||||
remote_ip => $remote_ip,
|
||||
remote_host => $remote_host,
|
||||
local_ip => $opts{conn}->local_ip,
|
||||
@_
|
||||
);
|
||||
remote_info => $remote_info,
|
||||
remote_ip => $remote_ip,
|
||||
remote_host => $remote_host,
|
||||
local_ip => $opts{conn}->local_ip,
|
||||
@_
|
||||
);
|
||||
}
|
||||
|
||||
sub config {
|
||||
@ -119,7 +123,8 @@ sub getline {
|
||||
my $bb = $self->{bb_in};
|
||||
|
||||
while (1) {
|
||||
my $rc = $c->input_filters->get_brigade($bb, Apache2::Const::MODE_GETLINE);
|
||||
my $rc =
|
||||
$c->input_filters->get_brigade($bb, Apache2::Const::MODE_GETLINE);
|
||||
return if $rc == APR::Const::EOF;
|
||||
die APR::Error::strerror($rc) unless $rc == APR::Const::SUCCESS;
|
||||
|
||||
@ -134,14 +139,14 @@ sub getline {
|
||||
|
||||
sub read_input {
|
||||
my $self = shift;
|
||||
my $c = $self->{conn};
|
||||
my $c = $self->{conn};
|
||||
|
||||
while (defined(my $data = $self->getline)) {
|
||||
$data =~ s/\r?\n$//s; # advanced chomp
|
||||
$data =~ s/\r?\n$//s; # advanced chomp
|
||||
$self->connection->notes('original_string', $data);
|
||||
$self->log(LOGDEBUG, "dispatching $data");
|
||||
defined $self->dispatch(split / +/, $data, 2)
|
||||
or $self->respond(502, "command unrecognized: '$data'");
|
||||
or $self->respond(502, "command unrecognized: '$data'");
|
||||
last if $self->{_quitting};
|
||||
}
|
||||
}
|
||||
@ -151,11 +156,12 @@ sub respond {
|
||||
my $c = $self->{conn};
|
||||
while (my $msg = shift @messages) {
|
||||
my $bb = $self->{bb_out};
|
||||
my $line = $code . (@messages?"-":" ").$msg;
|
||||
my $line = $code . (@messages ? "-" : " ") . $msg;
|
||||
$self->log(LOGDEBUG, $line);
|
||||
my $bucket = APR::Bucket->new(($c->bucket_alloc), "$line\r\n");
|
||||
$bb->insert_tail($bucket);
|
||||
$c->output_filters->fflush($bb);
|
||||
|
||||
# $bucket->remove;
|
||||
$bb->cleanup;
|
||||
}
|
||||
|
@ -3,26 +3,26 @@
|
||||
package Danga::Client;
|
||||
use base 'Danga::TimeoutSocket';
|
||||
use fields qw(
|
||||
line
|
||||
pause_count
|
||||
read_bytes
|
||||
data_bytes
|
||||
callback
|
||||
get_chunks
|
||||
reader_object
|
||||
);
|
||||
line
|
||||
pause_count
|
||||
read_bytes
|
||||
data_bytes
|
||||
callback
|
||||
get_chunks
|
||||
reader_object
|
||||
);
|
||||
use Time::HiRes ();
|
||||
|
||||
use bytes;
|
||||
|
||||
# 30 seconds max timeout!
|
||||
sub max_idle_time { 30 }
|
||||
sub max_connect_time { 1200 }
|
||||
sub max_idle_time { 30 }
|
||||
sub max_connect_time { 1200 }
|
||||
|
||||
sub new {
|
||||
my Danga::Client $self = shift;
|
||||
$self = fields::new($self) unless ref $self;
|
||||
$self->SUPER::new( @_ );
|
||||
$self->SUPER::new(@_);
|
||||
|
||||
$self->reset_for_next_message;
|
||||
return $self;
|
||||
@ -30,13 +30,13 @@ sub new {
|
||||
|
||||
sub reset_for_next_message {
|
||||
my Danga::Client $self = shift;
|
||||
$self->{line} = '';
|
||||
$self->{pause_count} = 0;
|
||||
$self->{read_bytes} = 0;
|
||||
$self->{callback} = undef;
|
||||
$self->{line} = '';
|
||||
$self->{pause_count} = 0;
|
||||
$self->{read_bytes} = 0;
|
||||
$self->{callback} = undef;
|
||||
$self->{reader_object} = undef;
|
||||
$self->{data_bytes} = '';
|
||||
$self->{get_chunks} = 0;
|
||||
$self->{data_bytes} = '';
|
||||
$self->{get_chunks} = 0;
|
||||
return $self;
|
||||
}
|
||||
|
||||
@ -52,10 +52,12 @@ sub get_bytes {
|
||||
$self->{line} = '';
|
||||
if ($self->{read_bytes} <= 0) {
|
||||
if ($self->{read_bytes} < 0) {
|
||||
$self->{line} = substr($self->{data_bytes},
|
||||
$self->{read_bytes}, # negative offset
|
||||
0 - $self->{read_bytes}, # to end of str
|
||||
""); # truncate that substr
|
||||
$self->{line} = substr(
|
||||
$self->{data_bytes},
|
||||
$self->{read_bytes}, # negative offset
|
||||
0 - $self->{read_bytes}, # to end of str
|
||||
""
|
||||
); # truncate that substr
|
||||
}
|
||||
$callback->($self->{data_bytes});
|
||||
return;
|
||||
@ -91,14 +93,14 @@ sub get_chunks {
|
||||
}
|
||||
$self->{read_bytes} = $bytes;
|
||||
$self->process_chunk($callback) if length($self->{line});
|
||||
$self->{callback} = $callback;
|
||||
$self->{callback} = $callback;
|
||||
$self->{get_chunks} = 1;
|
||||
}
|
||||
|
||||
sub end_get_chunks {
|
||||
my Danga::Client $self = shift;
|
||||
my $remaining = shift;
|
||||
$self->{callback} = undef;
|
||||
$self->{callback} = undef;
|
||||
$self->{get_chunks} = 0;
|
||||
if (defined($remaining)) {
|
||||
$self->process_read_buf(\$remaining);
|
||||
@ -132,6 +134,7 @@ sub event_read {
|
||||
$self->{data_bytes} .= $$bref;
|
||||
}
|
||||
if ($self->{read_bytes} <= 0) {
|
||||
|
||||
# print "Erk, read too much!\n" if $self->{read_bytes} < 0;
|
||||
my $cb = $self->{callback};
|
||||
$self->{callback} = undef;
|
||||
@ -155,16 +158,24 @@ sub process_read_buf {
|
||||
my $line = $1;
|
||||
$self->{alive_time} = time;
|
||||
my $resp = $self->process_line($line);
|
||||
if ($::DEBUG > 1 and $resp) { print "$$:".($self+0)."S: $_\n" for split(/\n/, $resp) }
|
||||
if ($::DEBUG > 1 and $resp) {
|
||||
print "$$:" . ($self + 0) . "S: $_\n" for split(/\n/, $resp);
|
||||
}
|
||||
$self->write($resp) if $resp;
|
||||
|
||||
# $self->watch_read(0) if $self->{pause_count};
|
||||
return if $self->{pause_count} || $self->{closed};
|
||||
|
||||
# read more in a timer, to give other clients a look in
|
||||
$self->AddTimer(0, sub {
|
||||
if (length($self->{line}) && !$self->paused) {
|
||||
$self->process_read_buf(\""); # " for bad syntax highlighters
|
||||
$self->AddTimer(
|
||||
0,
|
||||
sub {
|
||||
if (length($self->{line}) && !$self->paused) {
|
||||
$self->process_read_buf(\"")
|
||||
; # " for bad syntax highlighters
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,6 +199,7 @@ sub paused {
|
||||
sub pause_read {
|
||||
my Danga::Client $self = shift;
|
||||
$self->{pause_count}++;
|
||||
|
||||
# $self->watch_read(0);
|
||||
}
|
||||
|
||||
@ -196,11 +208,15 @@ sub continue_read {
|
||||
$self->{pause_count}--;
|
||||
if ($self->{pause_count} <= 0) {
|
||||
$self->{pause_count} = 0;
|
||||
$self->AddTimer(0, sub {
|
||||
if (length($self->{line}) && !$self->paused) {
|
||||
$self->process_read_buf(\""); # " for bad syntax highlighters
|
||||
$self->AddTimer(
|
||||
0,
|
||||
sub {
|
||||
if (length($self->{line}) && !$self->paused) {
|
||||
$self->process_read_buf(\"")
|
||||
; # " for bad syntax highlighters
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -216,6 +232,10 @@ sub close {
|
||||
}
|
||||
|
||||
sub event_err { my Danga::Client $self = shift; $self->close("Error") }
|
||||
sub event_hup { my Danga::Client $self = shift; $self->close("Disconnect (HUP)") }
|
||||
|
||||
sub event_hup {
|
||||
my Danga::Client $self = shift;
|
||||
$self->close("Disconnect (HUP)");
|
||||
}
|
||||
|
||||
1;
|
||||
|
@ -22,8 +22,8 @@ sub new {
|
||||
}
|
||||
|
||||
# overload these in a subclass
|
||||
sub max_idle_time { 0 }
|
||||
sub max_connect_time { 0 }
|
||||
sub max_idle_time { 0 }
|
||||
sub max_connect_time { 0 }
|
||||
|
||||
sub Reset {
|
||||
Danga::Socket->Reset;
|
||||
@ -37,16 +37,16 @@ sub _do_cleanup {
|
||||
|
||||
my $sf = __PACKAGE__->get_sock_ref;
|
||||
|
||||
my %max_age; # classname -> max age (0 means forever)
|
||||
my %max_connect; # classname -> max connect time
|
||||
my %max_age; # classname -> max age (0 means forever)
|
||||
my %max_connect; # classname -> max connect time
|
||||
my @to_close;
|
||||
while (my $k = each %$sf) {
|
||||
my Danga::TimeoutSocket $v = $sf->{$k};
|
||||
my $ref = ref $v;
|
||||
next unless $v->isa('Danga::TimeoutSocket');
|
||||
unless (defined $max_age{$ref}) {
|
||||
$max_age{$ref} = $ref->max_idle_time || 0;
|
||||
$max_connect{$ref} = $ref->max_connect_time || 0;
|
||||
$max_age{$ref} = $ref->max_idle_time || 0;
|
||||
$max_connect{$ref} = $ref->max_connect_time || 0;
|
||||
}
|
||||
if (my $t = $max_connect{$ref}) {
|
||||
if ($v->{create_time} < $now - $t) {
|
||||
|
850
lib/Qpsmtpd.pm
850
lib/Qpsmtpd.pm
File diff suppressed because it is too large
Load Diff
@ -25,9 +25,9 @@ for easy testing of values.
|
||||
=cut
|
||||
|
||||
use overload (
|
||||
'""' => \&format,
|
||||
'cmp' => \&_addr_cmp,
|
||||
);
|
||||
'""' => \&format,
|
||||
'cmp' => \&_addr_cmp,
|
||||
);
|
||||
|
||||
=head2 new()
|
||||
|
||||
@ -59,13 +59,13 @@ test for equality (like in badmailfrom).
|
||||
sub new {
|
||||
my ($class, $user, $host) = @_;
|
||||
my $self = {};
|
||||
if ($user =~ /^<(.*)>$/ ) {
|
||||
($user, $host) = $class->canonify($user);
|
||||
return undef unless defined $user;
|
||||
if ($user =~ /^<(.*)>$/) {
|
||||
($user, $host) = $class->canonify($user);
|
||||
return undef unless defined $user;
|
||||
}
|
||||
elsif ( not defined $host ) {
|
||||
my $address = $user;
|
||||
($user, $host) = $address =~ m/(.*)(?:\@(.*))/;
|
||||
elsif (not defined $host) {
|
||||
my $address = $user;
|
||||
($user, $host) = $address =~ m/(.*)(?:\@(.*))/;
|
||||
}
|
||||
$self->{_user} = $user;
|
||||
$self->{_host} = $host;
|
||||
@ -196,8 +196,11 @@ sub canonify {
|
||||
return undef unless ($path =~ /^<(.*)>$/);
|
||||
$path = $1;
|
||||
|
||||
my $domain = $domain_expr ? $domain_expr
|
||||
: "$subdomain_expr(?:\.$subdomain_expr)*";
|
||||
my $domain =
|
||||
$domain_expr
|
||||
? $domain_expr
|
||||
: "$subdomain_expr(?:\.$subdomain_expr)*";
|
||||
|
||||
# it is possible for $address_literal_expr to be empty, if a site
|
||||
# doesn't want to allow them
|
||||
$domain = "(?:$address_literal_expr|$domain)"
|
||||
@ -216,14 +219,15 @@ sub canonify {
|
||||
return (undef) unless defined $localpart;
|
||||
|
||||
if ($localpart =~ /^$atom_expr(\.$atom_expr)*/) {
|
||||
|
||||
# simple case, we are done
|
||||
return ($localpart, $domainpart);
|
||||
}
|
||||
}
|
||||
if ($localpart =~ /^"(($qtext_expr|\\$text_expr)*)"$/) {
|
||||
$localpart = $1;
|
||||
$localpart =~ s/\\($text_expr)/$1/g;
|
||||
return ($localpart, $domainpart);
|
||||
}
|
||||
}
|
||||
return (undef);
|
||||
}
|
||||
|
||||
@ -234,7 +238,7 @@ to new() called with a single parameter.
|
||||
|
||||
=cut
|
||||
|
||||
sub parse { # retain for compatibility only
|
||||
sub parse { # retain for compatibility only
|
||||
return shift->new(shift);
|
||||
}
|
||||
|
||||
@ -252,14 +256,14 @@ L<format>.
|
||||
|
||||
sub address {
|
||||
my ($self, $val) = @_;
|
||||
if ( defined($val) ) {
|
||||
$val = "<$val>" unless $val =~ /^<.+>$/;
|
||||
my ($user, $host) = $self->canonify($val);
|
||||
$self->{_user} = $user;
|
||||
$self->{_host} = $host;
|
||||
if (defined($val)) {
|
||||
$val = "<$val>" unless $val =~ /^<.+>$/;
|
||||
my ($user, $host) = $self->canonify($val);
|
||||
$self->{_user} = $user;
|
||||
$self->{_host} = $host;
|
||||
}
|
||||
return ( defined $self->{_user} ? $self->{_user} : '' )
|
||||
. ( defined $self->{_host} ? '@'.$self->{_host} : '' );
|
||||
return (defined $self->{_user} ? $self->{_user} : '')
|
||||
. (defined $self->{_host} ? '@' . $self->{_host} : '');
|
||||
}
|
||||
|
||||
=head2 format()
|
||||
@ -278,11 +282,12 @@ sub format {
|
||||
my ($self) = @_;
|
||||
my $qchar = '[^a-zA-Z0-9!#\$\%\&\x27\*\+\x2D\/=\?\^_`{\|}~.]';
|
||||
return '<>' unless defined $self->{_user};
|
||||
if ( ( my $user = $self->{_user}) =~ s/($qchar)/\\$1/g) {
|
||||
return qq(<"$user")
|
||||
. ( defined $self->{_host} ? '@'.$self->{_host} : '' ). ">";
|
||||
}
|
||||
return "<".$self->address().">";
|
||||
if ((my $user = $self->{_user}) =~ s/($qchar)/\\$1/g) {
|
||||
return
|
||||
qq(<"$user")
|
||||
. (defined $self->{_host} ? '@' . $self->{_host} : '') . ">";
|
||||
}
|
||||
return "<" . $self->address() . ">";
|
||||
}
|
||||
|
||||
=head2 user([$user])
|
||||
@ -326,10 +331,11 @@ use this to pass data between plugins.
|
||||
=cut
|
||||
|
||||
sub notes {
|
||||
my ($self,$key) = (shift,shift);
|
||||
# Check for any additional arguments passed by the caller -- including undef
|
||||
return $self->{_notes}->{$key} unless @_;
|
||||
return $self->{_notes}->{$key} = shift;
|
||||
my ($self, $key) = (shift, shift);
|
||||
|
||||
# Check for any additional arguments passed by the caller -- including undef
|
||||
return $self->{_notes}->{$key} unless @_;
|
||||
return $self->{_notes}->{$key} = shift;
|
||||
}
|
||||
|
||||
sub _addr_cmp {
|
||||
@ -337,16 +343,16 @@ sub _addr_cmp {
|
||||
my ($left, $right, $swap) = @_;
|
||||
my $class = ref($left);
|
||||
|
||||
unless ( UNIVERSAL::isa($right, $class) ) {
|
||||
$right = $class->new($right);
|
||||
unless (UNIVERSAL::isa($right, $class)) {
|
||||
$right = $class->new($right);
|
||||
}
|
||||
|
||||
#invert the address so we can sort by domain then user
|
||||
($left = join( '=', reverse( split(/@/, $left->format))) ) =~ tr/[<>]//d;
|
||||
($right = join( '=', reverse( split(/@/,$right->format))) ) =~ tr/[<>]//d;
|
||||
($left = join('=', reverse(split(/@/, $left->format)))) =~ tr/[<>]//d;
|
||||
($right = join('=', reverse(split(/@/, $right->format)))) =~ tr/[<>]//d;
|
||||
|
||||
if ( $swap ) {
|
||||
($right, $left) = ($left, $right);
|
||||
if ($swap) {
|
||||
($right, $left) = ($left, $right);
|
||||
}
|
||||
|
||||
return ($left cmp $right);
|
||||
|
@ -1,4 +1,5 @@
|
||||
package Qpsmtpd::Auth;
|
||||
|
||||
# See the documentation in 'perldoc docs/authentication.pod'
|
||||
|
||||
use strict;
|
||||
@ -10,167 +11,172 @@ use Digest::HMAC_MD5 qw(hmac_md5_hex);
|
||||
use MIME::Base64;
|
||||
|
||||
sub e64 {
|
||||
my ($arg) = @_;
|
||||
my $res = encode_base64($arg);
|
||||
chomp($res);
|
||||
return($res);
|
||||
my ($arg) = @_;
|
||||
my $res = encode_base64($arg);
|
||||
chomp($res);
|
||||
return ($res);
|
||||
}
|
||||
|
||||
sub SASL {
|
||||
|
||||
# $DB::single = 1;
|
||||
my ( $session, $mechanism, $prekey ) = @_;
|
||||
my ( $user, $passClear, $passHash, $ticket, $loginas );
|
||||
my ($session, $mechanism, $prekey) = @_;
|
||||
my ($user, $passClear, $passHash, $ticket, $loginas);
|
||||
|
||||
if ( $mechanism eq 'plain' ) {
|
||||
($loginas, $user, $passClear) = get_auth_details_plain($session,$prekey);
|
||||
return DECLINED if ! $user || ! $passClear;
|
||||
if ($mechanism eq 'plain') {
|
||||
($loginas, $user, $passClear) =
|
||||
get_auth_details_plain($session, $prekey);
|
||||
return DECLINED if !$user || !$passClear;
|
||||
}
|
||||
elsif ( $mechanism eq 'login' ) {
|
||||
($user, $passClear) = get_auth_details_login($session,$prekey);
|
||||
return DECLINED if ! $user || ! $passClear;
|
||||
elsif ($mechanism eq 'login') {
|
||||
($user, $passClear) = get_auth_details_login($session, $prekey);
|
||||
return DECLINED if !$user || !$passClear;
|
||||
}
|
||||
elsif ( $mechanism eq 'cram-md5' ) {
|
||||
( $ticket, $user, $passHash ) = get_auth_details_cram_md5($session);
|
||||
return DECLINED if ! $user || ! $passHash;
|
||||
elsif ($mechanism eq 'cram-md5') {
|
||||
($ticket, $user, $passHash) = get_auth_details_cram_md5($session);
|
||||
return DECLINED if !$user || !$passHash;
|
||||
}
|
||||
else {
|
||||
#this error is now caught in SMTP.pm's sub auth
|
||||
$session->respond( 500, "Internal server error" );
|
||||
$session->respond(500, "Internal server error");
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
# try running the specific hooks first
|
||||
my ( $rc, $msg ) =
|
||||
$session->run_hooks( "auth-$mechanism", $mechanism, $user, $passClear,
|
||||
$passHash, $ticket );
|
||||
my ($rc, $msg) =
|
||||
$session->run_hooks("auth-$mechanism", $mechanism, $user, $passClear,
|
||||
$passHash, $ticket);
|
||||
|
||||
# try running the polymorphous hooks next
|
||||
if ( !$rc || $rc == DECLINED ) {
|
||||
( $rc, $msg ) =
|
||||
$session->run_hooks( "auth", $mechanism, $user, $passClear,
|
||||
$passHash, $ticket );
|
||||
if (!$rc || $rc == DECLINED) {
|
||||
($rc, $msg) =
|
||||
$session->run_hooks("auth", $mechanism, $user,
|
||||
$passClear, $passHash, $ticket);
|
||||
}
|
||||
|
||||
if ( $rc == OK ) {
|
||||
$msg = uc($mechanism) . " authentication successful for $user" .
|
||||
( $msg ? " - $msg" : '');
|
||||
$session->respond( 235, $msg );
|
||||
if ($rc == OK) {
|
||||
$msg =
|
||||
uc($mechanism)
|
||||
. " authentication successful for $user"
|
||||
. ($msg ? " - $msg" : '');
|
||||
$session->respond(235, $msg);
|
||||
$session->connection->relay_client(1);
|
||||
if ( $session->connection->notes('naughty' ) ) {
|
||||
$session->log( LOGINFO, "auth success cleared naughty" );
|
||||
$session->connection->notes('naughty',0);
|
||||
};
|
||||
$session->log( LOGDEBUG, $msg ); # already logged by $session->respond
|
||||
if ($session->connection->notes('naughty')) {
|
||||
$session->log(LOGINFO, "auth success cleared naughty");
|
||||
$session->connection->notes('naughty', 0);
|
||||
}
|
||||
$session->log(LOGDEBUG, $msg); # already logged by $session->respond
|
||||
|
||||
$session->{_auth_user} = $user;
|
||||
$session->{_auth_user} = $user;
|
||||
$session->{_auth_mechanism} = $mechanism;
|
||||
s/[\r\n].*//s for ($session->{_auth_user}, $session->{_auth_mechanism});
|
||||
|
||||
return OK;
|
||||
}
|
||||
else {
|
||||
$msg = uc($mechanism) . " authentication failed for $user" .
|
||||
( $msg ? " - $msg" : '');
|
||||
$session->respond( 535, $msg );
|
||||
$session->log( LOGDEBUG, $msg ); # already logged by $session->respond
|
||||
$msg =
|
||||
uc($mechanism)
|
||||
. " authentication failed for $user"
|
||||
. ($msg ? " - $msg" : '');
|
||||
$session->respond(535, $msg);
|
||||
$session->log(LOGDEBUG, $msg); # already logged by $session->respond
|
||||
return DENY;
|
||||
}
|
||||
}
|
||||
|
||||
sub get_auth_details_plain {
|
||||
my ( $session, $prekey ) = @_;
|
||||
my ($session, $prekey) = @_;
|
||||
|
||||
if ( ! $prekey) {
|
||||
$session->respond( 334, ' ' );
|
||||
$prekey= <STDIN>;
|
||||
if (!$prekey) {
|
||||
$session->respond(334, ' ');
|
||||
$prekey = <STDIN>;
|
||||
}
|
||||
|
||||
my ( $loginas, $user, $passClear ) = split /\x0/, decode_base64($prekey);
|
||||
my ($loginas, $user, $passClear) = split /\x0/, decode_base64($prekey);
|
||||
|
||||
if ( ! $user ) {
|
||||
if ( $loginas ) {
|
||||
if (!$user) {
|
||||
if ($loginas) {
|
||||
$session->respond(535, "Authentication invalid ($loginas)");
|
||||
}
|
||||
else {
|
||||
$session->respond(535, "Authentication invalid");
|
||||
}
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
# Authorization ID must not be different from Authentication ID
|
||||
if ( $loginas ne '' && $loginas ne $user ) {
|
||||
if ($loginas ne '' && $loginas ne $user) {
|
||||
$session->respond(535, "Authentication invalid for $user");
|
||||
return;
|
||||
}
|
||||
|
||||
return ($loginas, $user, $passClear);
|
||||
};
|
||||
}
|
||||
|
||||
sub get_auth_details_login {
|
||||
my ( $session, $prekey ) = @_;
|
||||
my ($session, $prekey) = @_;
|
||||
|
||||
my $user;
|
||||
|
||||
if ( $prekey ) {
|
||||
if ($prekey) {
|
||||
$user = decode_base64($prekey);
|
||||
}
|
||||
else {
|
||||
$user = get_base64_response($session,'Username:') or return;
|
||||
$user = get_base64_response($session, 'Username:') or return;
|
||||
}
|
||||
|
||||
my $passClear = get_base64_response($session,'Password:') or return;
|
||||
my $passClear = get_base64_response($session, 'Password:') or return;
|
||||
|
||||
return ($user, $passClear);
|
||||
};
|
||||
}
|
||||
|
||||
sub get_auth_details_cram_md5 {
|
||||
my ( $session, $ticket ) = @_;
|
||||
my ($session, $ticket) = @_;
|
||||
|
||||
if ( ! $ticket ) { # ticket is only passed in during testing
|
||||
# rand() is not cryptographic, but we only need to generate a globally
|
||||
# unique number. The rand() is there in case the user logs in more than
|
||||
# once in the same second, or if the clock is skewed.
|
||||
$ticket = sprintf( '<%x.%x@%s>',
|
||||
rand(1000000), time(), $session->config('me') );
|
||||
};
|
||||
if (!$ticket) { # ticket is only passed in during testing
|
||||
# rand() is not cryptographic, but we only need to generate a globally
|
||||
# unique number. The rand() is there in case the user logs in more than
|
||||
# once in the same second, or if the clock is skewed.
|
||||
$ticket =
|
||||
sprintf('<%x.%x@%s>', rand(1000000), time(), $session->config('me'));
|
||||
}
|
||||
|
||||
# send the base64 encoded ticket
|
||||
$session->respond( 334, encode_base64( $ticket, '' ) );
|
||||
$session->respond(334, encode_base64($ticket, ''));
|
||||
my $line = <STDIN>;
|
||||
|
||||
if ( $line eq '*' ) {
|
||||
$session->respond( 501, "Authentication canceled" );
|
||||
if ($line eq '*') {
|
||||
$session->respond(501, "Authentication canceled");
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
my ( $user, $passHash ) = split( / /, decode_base64($line) );
|
||||
unless ( $user && $passHash ) {
|
||||
my ($user, $passHash) = split(/ /, decode_base64($line));
|
||||
unless ($user && $passHash) {
|
||||
$session->respond(504, "Invalid authentication string");
|
||||
return;
|
||||
}
|
||||
|
||||
$session->{auth}{ticket} = $ticket;
|
||||
return ($ticket, $user, $passHash);
|
||||
};
|
||||
}
|
||||
|
||||
sub get_base64_response {
|
||||
my ($session, $question) = @_;
|
||||
|
||||
$session->respond(334, e64($question));
|
||||
my $answer = decode_base64( <STDIN> );
|
||||
my $answer = decode_base64(<STDIN>);
|
||||
if ($answer eq '*') {
|
||||
$session->respond(501, "Authentication canceled");
|
||||
return;
|
||||
}
|
||||
return $answer;
|
||||
};
|
||||
}
|
||||
|
||||
sub validate_password {
|
||||
my ( $self, %a ) = @_;
|
||||
my ($self, %a) = @_;
|
||||
|
||||
my ($pkg, $file, $line) = caller();
|
||||
$file = (split /\//, $file)[-1]; # strip off the path
|
||||
$file = (split /\//, $file)[-1]; # strip off the path
|
||||
|
||||
my $src_clear = $a{src_clear};
|
||||
my $src_crypt = $a{src_crypt};
|
||||
@ -180,43 +186,43 @@ sub validate_password {
|
||||
my $ticket = $a{ticket} || $self->{auth}{ticket};
|
||||
my $deny = $a{deny} || DENY;
|
||||
|
||||
if ( ! $src_crypt && ! $src_clear ) {
|
||||
if (!$src_crypt && !$src_clear) {
|
||||
$self->log(LOGINFO, "fail: missing password");
|
||||
return ( $deny, "$file - no such user" );
|
||||
};
|
||||
|
||||
if ( ! $src_clear && $method =~ /CRAM-MD5/i ) {
|
||||
$self->log(LOGINFO, "skip: cram-md5 not supported w/o clear pass");
|
||||
return ( DECLINED, $file );
|
||||
return ($deny, "$file - no such user");
|
||||
}
|
||||
|
||||
if ( defined $attempt_clear ) {
|
||||
if ( $src_clear && $src_clear eq $attempt_clear ) {
|
||||
if (!$src_clear && $method =~ /CRAM-MD5/i) {
|
||||
$self->log(LOGINFO, "skip: cram-md5 not supported w/o clear pass");
|
||||
return (DECLINED, $file);
|
||||
}
|
||||
|
||||
if (defined $attempt_clear) {
|
||||
if ($src_clear && $src_clear eq $attempt_clear) {
|
||||
$self->log(LOGINFO, "pass: clear match");
|
||||
return ( OK, $file );
|
||||
};
|
||||
|
||||
if ( $src_crypt && $src_crypt eq crypt( $attempt_clear, $src_crypt ) ) {
|
||||
$self->log(LOGINFO, "pass: crypt match");
|
||||
return ( OK, $file );
|
||||
return (OK, $file);
|
||||
}
|
||||
};
|
||||
|
||||
if ( defined $attempt_hash && $src_clear ) {
|
||||
if ( ! $ticket ) {
|
||||
if ($src_crypt && $src_crypt eq crypt($attempt_clear, $src_crypt)) {
|
||||
$self->log(LOGINFO, "pass: crypt match");
|
||||
return (OK, $file);
|
||||
}
|
||||
}
|
||||
|
||||
if (defined $attempt_hash && $src_clear) {
|
||||
if (!$ticket) {
|
||||
$self->log(LOGERROR, "skip: missing ticket");
|
||||
return ( DECLINED, $file );
|
||||
};
|
||||
return (DECLINED, $file);
|
||||
}
|
||||
|
||||
if ( $attempt_hash eq hmac_md5_hex( $ticket, $src_clear ) ) {
|
||||
if ($attempt_hash eq hmac_md5_hex($ticket, $src_clear)) {
|
||||
$self->log(LOGINFO, "pass: hash match");
|
||||
return ( OK, $file );
|
||||
};
|
||||
};
|
||||
return (OK, $file);
|
||||
}
|
||||
}
|
||||
|
||||
$self->log(LOGINFO, "fail: wrong password");
|
||||
return ( $deny, "$file - wrong password" );
|
||||
};
|
||||
return ($deny, "$file - wrong password");
|
||||
}
|
||||
|
||||
# tag: qpsmtpd plugin that sets RELAYCLIENT when the user authenticates
|
||||
|
||||
|
@ -60,8 +60,8 @@ use vars qw(@ISA);
|
||||
@ISA = qw(Qpsmtpd::SMTP);
|
||||
|
||||
sub parse {
|
||||
my ($me,$cmd,$line,$sub) = @_;
|
||||
return (OK) unless defined $line; # trivial case
|
||||
my ($me, $cmd, $line, $sub) = @_;
|
||||
return (OK) unless defined $line; # trivial case
|
||||
my $self = {};
|
||||
bless $self, $me;
|
||||
$cmd = lc $cmd;
|
||||
@ -80,25 +80,26 @@ sub parse {
|
||||
}
|
||||
my $parse = "parse_$cmd";
|
||||
if ($self->can($parse)) {
|
||||
|
||||
# print "CMD=$cmd,line=$line\n";
|
||||
my @out = eval { $self->$parse($cmd, $line); };
|
||||
if ($@) {
|
||||
$self->log(LOGERROR, "$parse($cmd,$line) failed: $@");
|
||||
return(DENY, "Failed to parse line");
|
||||
return (DENY, "Failed to parse line");
|
||||
}
|
||||
return @out;
|
||||
}
|
||||
return(OK, split(/ +/, $line)); # default :)
|
||||
return (OK, split(/ +/, $line)); # default :)
|
||||
}
|
||||
|
||||
sub parse_rcpt {
|
||||
my ($self,$cmd,$line) = @_;
|
||||
my ($self, $cmd, $line) = @_;
|
||||
return (DENY, "Syntax error in command") unless $line =~ s/^to:\s*//i;
|
||||
return &_get_mail_params($cmd, $line);
|
||||
}
|
||||
|
||||
sub parse_mail {
|
||||
my ($self,$cmd,$line) = @_;
|
||||
my ($self, $cmd, $line) = @_;
|
||||
return (DENY, "Syntax error in command") unless $line =~ s/^from:\s*//i;
|
||||
return &_get_mail_params($cmd, $line);
|
||||
}
|
||||
@ -121,7 +122,7 @@ sub parse_mail {
|
||||
## inner-esmtp-cmd ::= ("MAIL FROM:" reverse-path) /
|
||||
## ("RCPT TO:" forward-path)
|
||||
sub _get_mail_params {
|
||||
my ($cmd,$line) = @_;
|
||||
my ($cmd, $line) = @_;
|
||||
my @params = ();
|
||||
$line =~ s/\s*$//;
|
||||
|
||||
@ -137,6 +138,7 @@ sub _get_mail_params {
|
||||
|
||||
# let's see if $line contains nothing and use the first value as address:
|
||||
if ($line) {
|
||||
|
||||
# parameter syntax error, i.e. not all of the arguments were
|
||||
# stripped by the while() loop:
|
||||
return (DENY, "Syntax error in parameters")
|
||||
@ -146,9 +148,9 @@ sub _get_mail_params {
|
||||
|
||||
$line = shift @params;
|
||||
if ($cmd eq "mail") {
|
||||
return (OK, "<>") unless $line; # 'MAIL FROM:' --> 'MAIL FROM:<>'
|
||||
return (OK, "<>") unless $line; # 'MAIL FROM:' --> 'MAIL FROM:<>'
|
||||
return (DENY, "Syntax error in parameters")
|
||||
if ($line =~ /\@.*\s/); # parameter syntax error
|
||||
if ($line =~ /\@.*\s/); # parameter syntax error
|
||||
}
|
||||
else {
|
||||
if ($line =~ /\@/) {
|
||||
|
@ -6,15 +6,15 @@ use Qpsmtpd::Constants;
|
||||
use strict;
|
||||
|
||||
use fields qw(
|
||||
_auth
|
||||
_commands
|
||||
_config_cache
|
||||
_connection
|
||||
_transaction
|
||||
_test_mode
|
||||
_extras
|
||||
other_fds
|
||||
);
|
||||
_auth
|
||||
_commands
|
||||
_config_cache
|
||||
_connection
|
||||
_transaction
|
||||
_test_mode
|
||||
_extras
|
||||
other_fds
|
||||
);
|
||||
|
||||
my $PROMPT = "Enter command: ";
|
||||
|
||||
@ -22,22 +22,22 @@ sub new {
|
||||
my Qpsmtpd::ConfigServer $self = shift;
|
||||
|
||||
$self = fields::new($self) unless ref $self;
|
||||
$self->SUPER::new( @_ );
|
||||
$self->SUPER::new(@_);
|
||||
$self->write($PROMPT);
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub max_idle_time { 3600 } # one hour
|
||||
sub max_idle_time { 3600 } # one hour
|
||||
|
||||
sub process_line {
|
||||
my $self = shift;
|
||||
my $line = shift || return;
|
||||
if ($::DEBUG > 1) { print "$$:".($self+0)."C($self->{mode}): $line"; }
|
||||
if ($::DEBUG > 1) { print "$$:" . ($self + 0) . "C($self->{mode}): $line"; }
|
||||
local $SIG{ALRM} = sub {
|
||||
my ($pkg, $file, $line) = caller();
|
||||
die "ALARM: $pkg, $file, $line";
|
||||
};
|
||||
my $prev = alarm(2); # must process a command in < 2 seconds
|
||||
my $prev = alarm(2); # must process a command in < 2 seconds
|
||||
my $resp = eval { $self->_process_line($line) };
|
||||
alarm($prev);
|
||||
if ($@) {
|
||||
@ -56,11 +56,11 @@ sub respond {
|
||||
}
|
||||
|
||||
sub fault {
|
||||
my $self = shift;
|
||||
my ($msg) = shift || "program fault - command not performed";
|
||||
print STDERR "$0 [$$]: $msg ($!)\n";
|
||||
$self->respond("Error - " . $msg);
|
||||
return $PROMPT;
|
||||
my $self = shift;
|
||||
my ($msg) = shift || "program fault - command not performed";
|
||||
print STDERR "$0 [$$]: $msg ($!)\n";
|
||||
$self->respond("Error - " . $msg);
|
||||
return $PROMPT;
|
||||
}
|
||||
|
||||
sub _process_line {
|
||||
@ -71,9 +71,7 @@ sub _process_line {
|
||||
my ($cmd, @params) = split(/ +/, $line);
|
||||
my $meth = "cmd_" . lc($cmd);
|
||||
if (my $lookup = $self->can($meth)) {
|
||||
my $resp = eval {
|
||||
$lookup->($self, @params);
|
||||
};
|
||||
my $resp = eval { $lookup->($self, @params); };
|
||||
if ($@) {
|
||||
my $error = $@;
|
||||
chomp($error);
|
||||
@ -89,15 +87,17 @@ sub _process_line {
|
||||
}
|
||||
|
||||
my %helptext = (
|
||||
help => "HELP [CMD] - Get help on all commands or a specific command",
|
||||
help => "HELP [CMD] - Get help on all commands or a specific command",
|
||||
status => "STATUS - Returns status information about current connections",
|
||||
list => "LIST [LIMIT] - List the connections, specify limit or negative limit to shrink list",
|
||||
kill => "KILL (\$IP | \$REF) - Disconnect all connections from \$IP or connection reference \$REF",
|
||||
pause => "PAUSE - Stop accepting new connections",
|
||||
list =>
|
||||
"LIST [LIMIT] - List the connections, specify limit or negative limit to shrink list",
|
||||
kill =>
|
||||
"KILL (\$IP | \$REF) - Disconnect all connections from \$IP or connection reference \$REF",
|
||||
pause => "PAUSE - Stop accepting new connections",
|
||||
continue => "CONTINUE - Resume accepting connections",
|
||||
reload => "RELOAD - Reload all plugins and config",
|
||||
quit => "QUIT - Exit the config server",
|
||||
);
|
||||
reload => "RELOAD - Reload all plugins and config",
|
||||
quit => "QUIT - Exit the config server",
|
||||
);
|
||||
|
||||
sub cmd_help {
|
||||
my $self = shift;
|
||||
@ -107,10 +107,13 @@ sub cmd_help {
|
||||
$subcmd = lc($subcmd);
|
||||
|
||||
if ($subcmd eq 'help') {
|
||||
my $txt = join("\n", map { substr($_, 0, index($_, "-")) } sort values(%helptext));
|
||||
my $txt = join("\n",
|
||||
map { substr($_, 0, index($_, "-")) }
|
||||
sort values(%helptext));
|
||||
return "Available Commands:\n\n$txt\n";
|
||||
}
|
||||
my $txt = $helptext{$subcmd} || "Unrecognised help option. Try 'help' for a full list.";
|
||||
my $txt = $helptext{$subcmd}
|
||||
|| "Unrecognised help option. Try 'help' for a full list.";
|
||||
return "$txt\n";
|
||||
}
|
||||
|
||||
@ -128,7 +131,7 @@ sub cmd_pause {
|
||||
|
||||
my $other_fds = $self->OtherFds;
|
||||
|
||||
$self->{other_fds} = { %$other_fds };
|
||||
$self->{other_fds} = {%$other_fds};
|
||||
%$other_fds = ();
|
||||
return "PAUSED";
|
||||
}
|
||||
@ -138,7 +141,7 @@ sub cmd_continue {
|
||||
|
||||
my $other_fds = $self->{other_fds};
|
||||
|
||||
$self->OtherFds( %$other_fds );
|
||||
$self->OtherFds(%$other_fds);
|
||||
%$other_fds = ();
|
||||
return "UNPAUSED";
|
||||
}
|
||||
@ -146,18 +149,19 @@ sub cmd_continue {
|
||||
sub cmd_status {
|
||||
my $self = shift;
|
||||
|
||||
# Status should show:
|
||||
# - Total time running
|
||||
# - Total number of mails received
|
||||
# - Total number of mails rejected (5xx)
|
||||
# - Total number of mails tempfailed (5xx)
|
||||
# - Avg number of mails/minute
|
||||
# - Number of current connections
|
||||
# - Number of outstanding DNS queries
|
||||
# Status should show:
|
||||
# - Total time running
|
||||
# - Total number of mails received
|
||||
# - Total number of mails rejected (5xx)
|
||||
# - Total number of mails tempfailed (5xx)
|
||||
# - Avg number of mails/minute
|
||||
# - Number of current connections
|
||||
# - Number of outstanding DNS queries
|
||||
|
||||
my $output = "Current Status as of " . gmtime() . " GMT\n\n";
|
||||
|
||||
if (defined &Qpsmtpd::Plugin::stats::get_stats) {
|
||||
|
||||
# Stats plugin is loaded
|
||||
$output .= Qpsmtpd::Plugin::stats->get_stats;
|
||||
}
|
||||
@ -165,7 +169,7 @@ sub cmd_status {
|
||||
my $descriptors = Danga::Socket->DescriptorMap;
|
||||
|
||||
my $current_connections = 0;
|
||||
my $current_dns = 0;
|
||||
my $current_dns = 0;
|
||||
foreach my $fd (keys %$descriptors) {
|
||||
my $pob = $descriptors->{$fd};
|
||||
if ($pob->isa("Qpsmtpd::PollServer")) {
|
||||
@ -176,8 +180,8 @@ sub cmd_status {
|
||||
}
|
||||
}
|
||||
|
||||
$output .= "Curr Connections: $current_connections / $::MAXconn\n".
|
||||
"Curr DNS Queries: $current_dns";
|
||||
$output .= "Curr Connections: $current_connections / $::MAXconn\n"
|
||||
. "Curr DNS Queries: $current_dns";
|
||||
|
||||
return $output;
|
||||
}
|
||||
@ -188,28 +192,35 @@ sub cmd_list {
|
||||
|
||||
my $descriptors = Danga::Socket->DescriptorMap;
|
||||
|
||||
my $list = "Current" . ($count ? (($count > 0) ? " Oldest $count" : " Newest ".-$count) : "") . " Connections: \n\n";
|
||||
my $list =
|
||||
"Current"
|
||||
. ($count ? (($count > 0) ? " Oldest $count" : " Newest " . -$count) : "")
|
||||
. " Connections: \n\n";
|
||||
my @all;
|
||||
foreach my $fd (keys %$descriptors) {
|
||||
my $pob = $descriptors->{$fd};
|
||||
if ($pob->isa("Qpsmtpd::PollServer")) {
|
||||
next unless $pob->connection->remote_ip; # haven't even started yet
|
||||
push @all, [$pob+0, $pob->connection->remote_ip,
|
||||
$pob->connection->remote_host, $pob->uptime];
|
||||
next unless $pob->connection->remote_ip; # haven't even started yet
|
||||
push @all,
|
||||
[
|
||||
$pob + 0, $pob->connection->remote_ip,
|
||||
$pob->connection->remote_host, $pob->uptime
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@all = sort { $a->[3] <=> $b->[3] } @all;
|
||||
if ($count) {
|
||||
if ($count > 0) {
|
||||
@all = @all[$#all-($count-1) .. $#all];
|
||||
@all = @all[$#all - ($count - 1) .. $#all];
|
||||
}
|
||||
else {
|
||||
@all = @all[0..(abs($count) - 1)];
|
||||
@all = @all[0 .. (abs($count) - 1)];
|
||||
}
|
||||
}
|
||||
foreach my $item (@all) {
|
||||
$list .= sprintf("%x : %s [%s] Connected %0.2fs\n", map { defined()?$_:'' } @$item);
|
||||
$list .= sprintf("%x : %s [%s] Connected %0.2fs\n",
|
||||
map { defined() ? $_ : '' } @$item);
|
||||
}
|
||||
|
||||
return $list;
|
||||
@ -229,17 +240,20 @@ sub cmd_kill {
|
||||
my $pob = $descriptors->{$fd};
|
||||
if ($pob->isa("Qpsmtpd::PollServer")) {
|
||||
if ($is_ip) {
|
||||
next unless $pob->connection->remote_ip; # haven't even started yet
|
||||
next
|
||||
unless $pob->connection->remote_ip; # haven't even started yet
|
||||
if ($pob->connection->remote_ip eq $match) {
|
||||
$pob->write("550 Your connection has been killed by an administrator\r\n");
|
||||
$pob->write(
|
||||
"550 Your connection has been killed by an administrator\r\n");
|
||||
$pob->disconnect;
|
||||
$killed++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
# match by ID
|
||||
if ($pob+0 == hex($match)) {
|
||||
$pob->write("550 Your connection has been killed by an administrator\r\n");
|
||||
if ($pob + 0 == hex($match)) {
|
||||
$pob->write(
|
||||
"550 Your connection has been killed by an administrator\r\n");
|
||||
$pob->disconnect;
|
||||
$killed++;
|
||||
}
|
||||
@ -256,13 +270,13 @@ sub cmd_dump {
|
||||
|
||||
return "SYNTAX: DUMP \$REF\n" unless $ref;
|
||||
require Data::Dumper;
|
||||
$Data::Dumper::Indent=1;
|
||||
$Data::Dumper::Indent = 1;
|
||||
|
||||
my $descriptors = Danga::Socket->DescriptorMap;
|
||||
foreach my $fd (keys %$descriptors) {
|
||||
my $pob = $descriptors->{$fd};
|
||||
if ($pob->isa("Qpsmtpd::PollServer")) {
|
||||
if ($pob+0 == hex($ref)) {
|
||||
if ($pob + 0 == hex($ref)) {
|
||||
return Data::Dumper::Dumper($pob);
|
||||
}
|
||||
}
|
||||
|
@ -6,118 +6,119 @@ use strict;
|
||||
# are an appropriate set to use for either start() or clone(). Do
|
||||
# not add parameters here unless they also meet that criteria.
|
||||
my @parameters = qw(
|
||||
remote_host
|
||||
remote_ip
|
||||
remote_info
|
||||
remote_port
|
||||
local_ip
|
||||
local_port
|
||||
relay_client
|
||||
);
|
||||
|
||||
remote_host
|
||||
remote_ip
|
||||
remote_info
|
||||
remote_port
|
||||
local_ip
|
||||
local_port
|
||||
relay_client
|
||||
);
|
||||
|
||||
sub new {
|
||||
my $proto = shift;
|
||||
my $class = ref($proto) || $proto;
|
||||
my $self = {};
|
||||
bless ($self, $class);
|
||||
my $proto = shift;
|
||||
my $class = ref($proto) || $proto;
|
||||
my $self = {};
|
||||
bless($self, $class);
|
||||
}
|
||||
|
||||
sub start {
|
||||
my $self = shift;
|
||||
$self = $self->new(@_) unless ref $self;
|
||||
my $self = shift;
|
||||
$self = $self->new(@_) unless ref $self;
|
||||
|
||||
my %args = @_;
|
||||
my %args = @_;
|
||||
|
||||
foreach my $f ( @parameters ) {
|
||||
$self->$f($args{$f}) if $args{$f};
|
||||
}
|
||||
foreach my $f (@parameters) {
|
||||
$self->$f($args{$f}) if $args{$f};
|
||||
}
|
||||
|
||||
return $self;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub clone {
|
||||
my $self = shift;
|
||||
my %args = @_;
|
||||
my $new = $self->new();
|
||||
foreach my $f ( @parameters ) {
|
||||
$new->$f($self->$f()) if $self->$f();
|
||||
}
|
||||
$new->{_notes} = $self->{_notes} if defined $self->{_notes};
|
||||
# reset the old connection object like it's done at the end of a connection
|
||||
# to prevent leaks (like prefork/tls problem with the old SSL file handle
|
||||
# still around)
|
||||
$self->reset unless $args{no_reset};
|
||||
# should we generate a new id here?
|
||||
return $new;
|
||||
my $self = shift;
|
||||
my %args = @_;
|
||||
my $new = $self->new();
|
||||
foreach my $f (@parameters) {
|
||||
$new->$f($self->$f()) if $self->$f();
|
||||
}
|
||||
$new->{_notes} = $self->{_notes} if defined $self->{_notes};
|
||||
|
||||
# reset the old connection object like it's done at the end of a connection
|
||||
# to prevent leaks (like prefork/tls problem with the old SSL file handle
|
||||
# still around)
|
||||
$self->reset unless $args{no_reset};
|
||||
|
||||
# should we generate a new id here?
|
||||
return $new;
|
||||
}
|
||||
|
||||
sub remote_host {
|
||||
my $self = shift;
|
||||
@_ and $self->{_remote_host} = shift;
|
||||
$self->{_remote_host};
|
||||
my $self = shift;
|
||||
@_ and $self->{_remote_host} = shift;
|
||||
$self->{_remote_host};
|
||||
}
|
||||
|
||||
sub remote_ip {
|
||||
my $self = shift;
|
||||
@_ and $self->{_remote_ip} = shift;
|
||||
$self->{_remote_ip};
|
||||
my $self = shift;
|
||||
@_ and $self->{_remote_ip} = shift;
|
||||
$self->{_remote_ip};
|
||||
}
|
||||
|
||||
sub remote_port {
|
||||
my $self = shift;
|
||||
@_ and $self->{_remote_port} = shift;
|
||||
$self->{_remote_port};
|
||||
my $self = shift;
|
||||
@_ and $self->{_remote_port} = shift;
|
||||
$self->{_remote_port};
|
||||
}
|
||||
|
||||
sub local_ip {
|
||||
my $self = shift;
|
||||
@_ and $self->{_local_ip} = shift;
|
||||
$self->{_local_ip};
|
||||
my $self = shift;
|
||||
@_ and $self->{_local_ip} = shift;
|
||||
$self->{_local_ip};
|
||||
}
|
||||
|
||||
sub local_port {
|
||||
my $self = shift;
|
||||
@_ and $self->{_local_port} = shift;
|
||||
$self->{_local_port};
|
||||
my $self = shift;
|
||||
@_ and $self->{_local_port} = shift;
|
||||
$self->{_local_port};
|
||||
}
|
||||
|
||||
|
||||
sub remote_info {
|
||||
my $self = shift;
|
||||
@_ and $self->{_remote_info} = shift;
|
||||
$self->{_remote_info};
|
||||
my $self = shift;
|
||||
@_ and $self->{_remote_info} = shift;
|
||||
$self->{_remote_info};
|
||||
}
|
||||
|
||||
sub relay_client {
|
||||
my $self = shift;
|
||||
@_ and $self->{_relay_client} = shift;
|
||||
$self->{_relay_client};
|
||||
my $self = shift;
|
||||
@_ and $self->{_relay_client} = shift;
|
||||
$self->{_relay_client};
|
||||
}
|
||||
|
||||
sub hello {
|
||||
my $self = shift;
|
||||
@_ and $self->{_hello} = shift;
|
||||
$self->{_hello};
|
||||
my $self = shift;
|
||||
@_ and $self->{_hello} = shift;
|
||||
$self->{_hello};
|
||||
}
|
||||
|
||||
sub hello_host {
|
||||
my $self = shift;
|
||||
@_ and $self->{_hello_host} = shift;
|
||||
$self->{_hello_host};
|
||||
my $self = shift;
|
||||
@_ and $self->{_hello_host} = shift;
|
||||
$self->{_hello_host};
|
||||
}
|
||||
|
||||
sub notes {
|
||||
my ($self,$key) = (shift,shift);
|
||||
# Check for any additional arguments passed by the caller -- including undef
|
||||
return $self->{_notes}->{$key} unless @_;
|
||||
return $self->{_notes}->{$key} = shift;
|
||||
my ($self, $key) = (shift, shift);
|
||||
|
||||
# Check for any additional arguments passed by the caller -- including undef
|
||||
return $self->{_notes}->{$key} unless @_;
|
||||
return $self->{_notes}->{$key} = shift;
|
||||
}
|
||||
|
||||
sub reset {
|
||||
my $self = shift;
|
||||
$self->{_notes} = undef;
|
||||
$self = $self->new;
|
||||
my $self = shift;
|
||||
$self->{_notes} = undef;
|
||||
$self = $self->new;
|
||||
}
|
||||
|
||||
1;
|
||||
|
@ -4,64 +4,64 @@ require Exporter;
|
||||
|
||||
# log levels
|
||||
my %log_levels = (
|
||||
LOGDEBUG => 7,
|
||||
LOGINFO => 6,
|
||||
LOGNOTICE => 5,
|
||||
LOGWARN => 4,
|
||||
LOGERROR => 3,
|
||||
LOGCRIT => 2,
|
||||
LOGALERT => 1,
|
||||
LOGEMERG => 0,
|
||||
LOGRADAR => 0,
|
||||
);
|
||||
LOGDEBUG => 7,
|
||||
LOGINFO => 6,
|
||||
LOGNOTICE => 5,
|
||||
LOGWARN => 4,
|
||||
LOGERROR => 3,
|
||||
LOGCRIT => 2,
|
||||
LOGALERT => 1,
|
||||
LOGEMERG => 0,
|
||||
LOGRADAR => 0,
|
||||
);
|
||||
|
||||
# return codes
|
||||
my %return_codes = (
|
||||
OK => 900,
|
||||
DENY => 901, # 550
|
||||
DENYSOFT => 902, # 450
|
||||
DENYHARD => 903, # 550 + disconnect (deprecated in 0.29)
|
||||
DENY_DISCONNECT => 903, # 550 + disconnect
|
||||
DENYSOFT_DISCONNECT => 904, # 450 + disconnect
|
||||
DECLINED => 909,
|
||||
DONE => 910,
|
||||
CONTINUATION => 911, # deprecated - use YIELD
|
||||
YIELD => 911,
|
||||
);
|
||||
OK => 900,
|
||||
DENY => 901, # 550
|
||||
DENYSOFT => 902, # 450
|
||||
DENYHARD => 903, # 550 + disconnect (deprecated in 0.29)
|
||||
DENY_DISCONNECT => 903, # 550 + disconnect
|
||||
DENYSOFT_DISCONNECT => 904, # 450 + disconnect
|
||||
DECLINED => 909,
|
||||
DONE => 910,
|
||||
CONTINUATION => 911, # deprecated - use YIELD
|
||||
YIELD => 911,
|
||||
);
|
||||
|
||||
use vars qw(@ISA @EXPORT);
|
||||
@ISA = qw(Exporter);
|
||||
@EXPORT = (keys(%return_codes), keys(%log_levels), "return_code", "log_level");
|
||||
|
||||
foreach (keys %return_codes ) {
|
||||
eval "use constant $_ => ".$return_codes{$_};
|
||||
foreach (keys %return_codes) {
|
||||
eval "use constant $_ => " . $return_codes{$_};
|
||||
}
|
||||
|
||||
foreach (keys %log_levels ) {
|
||||
eval "use constant $_ => ".$log_levels{$_};
|
||||
foreach (keys %log_levels) {
|
||||
eval "use constant $_ => " . $log_levels{$_};
|
||||
}
|
||||
|
||||
sub return_code {
|
||||
my $test = shift;
|
||||
if ( $test =~ /^\d+$/ ) { # need to return the textural form
|
||||
foreach ( keys %return_codes ) {
|
||||
return $_ if $return_codes{$_} =~ /$test/;
|
||||
}
|
||||
if ($test =~ /^\d+$/) { # need to return the textural form
|
||||
foreach (keys %return_codes) {
|
||||
return $_ if $return_codes{$_} =~ /$test/;
|
||||
}
|
||||
}
|
||||
else { # just return the numeric value
|
||||
return $return_codes{$test};
|
||||
else { # just return the numeric value
|
||||
return $return_codes{$test};
|
||||
}
|
||||
}
|
||||
|
||||
sub log_level {
|
||||
my $test = shift;
|
||||
if ( $test =~ /^\d+$/ ) { # need to return the textural form
|
||||
foreach ( keys %log_levels ) {
|
||||
return $_ if $log_levels{$_} =~ /$test/;
|
||||
}
|
||||
if ($test =~ /^\d+$/) { # need to return the textural form
|
||||
foreach (keys %log_levels) {
|
||||
return $_ if $log_levels{$_} =~ /$test/;
|
||||
}
|
||||
}
|
||||
else { # just return the numeric value
|
||||
return $log_levels{$test};
|
||||
else { # just return the numeric value
|
||||
return $log_levels{$test};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,75 +49,75 @@ than the RFC message.
|
||||
|
||||
my @rfc1893 = (
|
||||
[
|
||||
"Other or Undefined Status", # x.0.x
|
||||
"Other or Undefined Status", # x.0.x
|
||||
],
|
||||
[
|
||||
"Other address status.", # x.1.0
|
||||
"Bad destination mailbox address.", # x.1.1
|
||||
"Bad destination system address.", # x.1.2
|
||||
"Bad destination mailbox address syntax.", # x.1.3
|
||||
"Destination mailbox address ambiguous.", # x.1.4
|
||||
"Destination address valid.", # x.1.5
|
||||
"Destination mailbox has moved, No forwarding address.", # x.1.6
|
||||
"Bad sender's mailbox address syntax.", # x.1.7
|
||||
"Bad sender's system address.", # x.1.8
|
||||
"Other address status.", # x.1.0
|
||||
"Bad destination mailbox address.", # x.1.1
|
||||
"Bad destination system address.", # x.1.2
|
||||
"Bad destination mailbox address syntax.", # x.1.3
|
||||
"Destination mailbox address ambiguous.", # x.1.4
|
||||
"Destination address valid.", # x.1.5
|
||||
"Destination mailbox has moved, No forwarding address.", # x.1.6
|
||||
"Bad sender's mailbox address syntax.", # x.1.7
|
||||
"Bad sender's system address.", # x.1.8
|
||||
],
|
||||
[
|
||||
"Other or undefined mailbox status.", # x.2.0
|
||||
"Mailbox disabled, not accepting messages.", # x.2.1
|
||||
"Mailbox full.", # x.2.2
|
||||
"Message length exceeds administrative limit.", # x.2.3
|
||||
"Mailing list expansion problem.", # x.2.4
|
||||
"Other or undefined mailbox status.", # x.2.0
|
||||
"Mailbox disabled, not accepting messages.", # x.2.1
|
||||
"Mailbox full.", # x.2.2
|
||||
"Message length exceeds administrative limit.", # x.2.3
|
||||
"Mailing list expansion problem.", # x.2.4
|
||||
],
|
||||
[
|
||||
"Other or undefined mail system status.", # x.3.0
|
||||
"Mail system full.", # x.3.1
|
||||
"System not accepting network messages.", # x.3.2
|
||||
"System not capable of selected features.", # x.3.3
|
||||
"Message too big for system.", # x.3.4
|
||||
"System incorrectly configured.", # x.3.5
|
||||
"Other or undefined mail system status.", # x.3.0
|
||||
"Mail system full.", # x.3.1
|
||||
"System not accepting network messages.", # x.3.2
|
||||
"System not capable of selected features.", # x.3.3
|
||||
"Message too big for system.", # x.3.4
|
||||
"System incorrectly configured.", # x.3.5
|
||||
],
|
||||
[
|
||||
"Other or undefined network or routing status.", # x.4.0
|
||||
"No answer from host.", # x.4.1
|
||||
"Bad connection.", # x.4.2
|
||||
"Directory server failure.", # x.4.3
|
||||
"Unable to route.", # x.4.4
|
||||
"Mail system congestion.", # x.4.5
|
||||
"Routing loop detected.", # x.4.6
|
||||
"Delivery time expired.", # x.4.7
|
||||
"Other or undefined network or routing status.", # x.4.0
|
||||
"No answer from host.", # x.4.1
|
||||
"Bad connection.", # x.4.2
|
||||
"Directory server failure.", # x.4.3
|
||||
"Unable to route.", # x.4.4
|
||||
"Mail system congestion.", # x.4.5
|
||||
"Routing loop detected.", # x.4.6
|
||||
"Delivery time expired.", # x.4.7
|
||||
],
|
||||
[
|
||||
"Other or undefined protocol status.", # x.5.0
|
||||
"Invalid command.", # x.5.1
|
||||
"Syntax error.", # x.5.2
|
||||
"Too many recipients.", # x.5.3
|
||||
"Invalid command arguments.", # x.5.4
|
||||
"Wrong protocol version.", # x.5.5
|
||||
"Other or undefined protocol status.", # x.5.0
|
||||
"Invalid command.", # x.5.1
|
||||
"Syntax error.", # x.5.2
|
||||
"Too many recipients.", # x.5.3
|
||||
"Invalid command arguments.", # x.5.4
|
||||
"Wrong protocol version.", # x.5.5
|
||||
],
|
||||
[
|
||||
"Other or undefined media error.", # x.6.0
|
||||
"Media not supported.", # x.6.1
|
||||
"Conversion required and prohibited.", # x.6.2
|
||||
"Conversion required but not supported.", # x.6.3
|
||||
"Conversion with loss performed.", # x.6.4
|
||||
"Conversion Failed.", # x.6.5
|
||||
"Other or undefined media error.", # x.6.0
|
||||
"Media not supported.", # x.6.1
|
||||
"Conversion required and prohibited.", # x.6.2
|
||||
"Conversion required but not supported.", # x.6.3
|
||||
"Conversion with loss performed.", # x.6.4
|
||||
"Conversion Failed.", # x.6.5
|
||||
],
|
||||
[
|
||||
"Other or undefined security status.", # x.7.0
|
||||
"Delivery not authorized, message refused.", # x.7.1
|
||||
"Mailing list expansion prohibited.", # x.7.2
|
||||
"Security conversion required but not possible.", # x.7.3
|
||||
"Security features not supported.", # x.7.4
|
||||
"Cryptographic failure.", # x.7.5
|
||||
"Cryptographic algorithm not supported.", # x.7.6
|
||||
"Message integrity failure.", # x.7.7
|
||||
"Other or undefined security status.", # x.7.0
|
||||
"Delivery not authorized, message refused.", # x.7.1
|
||||
"Mailing list expansion prohibited.", # x.7.2
|
||||
"Security conversion required but not possible.", # x.7.3
|
||||
"Security features not supported.", # x.7.4
|
||||
"Cryptographic failure.", # x.7.5
|
||||
"Cryptographic algorithm not supported.", # x.7.6
|
||||
"Message integrity failure.", # x.7.7
|
||||
],
|
||||
);
|
||||
|
||||
sub _status {
|
||||
my $return = shift;
|
||||
my $const = Qpsmtpd::Constants::return_code($return);
|
||||
my $const = Qpsmtpd::Constants::return_code($return);
|
||||
if ($const =~ /^DENYSOFT/) {
|
||||
return 4;
|
||||
}
|
||||
@ -127,13 +127,13 @@ sub _status {
|
||||
elsif ($const eq 'OK' or $const eq 'DONE') {
|
||||
return 2;
|
||||
}
|
||||
else { # err .... no :)
|
||||
return 4; # just 2,4,5 are allowed.. temp error by default
|
||||
else { # err .... no :)
|
||||
return 4; # just 2,4,5 are allowed.. temp error by default
|
||||
}
|
||||
}
|
||||
|
||||
sub _dsn {
|
||||
my ($self,$return,$reason,$default,$subject,$detail) = @_;
|
||||
my ($self, $return, $reason, $default, $subject, $detail) = @_;
|
||||
if (!defined $return) {
|
||||
$return = $default;
|
||||
}
|
||||
@ -157,7 +157,7 @@ sub _dsn {
|
||||
return ($return, "$msg (#$class.$subject.$detail)");
|
||||
}
|
||||
|
||||
sub unspecified { shift->_dsn(shift,shift,DENYSOFT,0,0); }
|
||||
sub unspecified { shift->_dsn(shift, shift, DENYSOFT, 0, 0); }
|
||||
|
||||
=head1 ADDRESS STATUS
|
||||
|
||||
@ -170,7 +170,7 @@ default: DENYSOFT
|
||||
|
||||
=cut
|
||||
|
||||
sub addr_unspecified { shift->_dsn(shift,shift,DENYSOFT,1,0); }
|
||||
sub addr_unspecified { shift->_dsn(shift, shift, DENYSOFT, 1, 0); }
|
||||
|
||||
=item no_such_user, addr_bad_dest_mbox
|
||||
|
||||
@ -179,8 +179,8 @@ default: DENY
|
||||
|
||||
=cut
|
||||
|
||||
sub no_such_user { shift->_dsn(shift,(shift||"No such user"),DENY,1,1); }
|
||||
sub addr_bad_dest_mbox { shift->_dsn(shift,shift,DENY,1,1); }
|
||||
sub no_such_user { shift->_dsn(shift, (shift || "No such user"), DENY, 1, 1); }
|
||||
sub addr_bad_dest_mbox { shift->_dsn(shift, shift, DENY, 1, 1); }
|
||||
|
||||
=item addr_bad_dest_system
|
||||
|
||||
@ -189,7 +189,7 @@ default: DENY
|
||||
|
||||
=cut
|
||||
|
||||
sub addr_bad_dest_system { shift->_dsn(shift,shift,DENY,1,2); }
|
||||
sub addr_bad_dest_system { shift->_dsn(shift, shift, DENY, 1, 2); }
|
||||
|
||||
=item addr_bad_dest_syntax
|
||||
|
||||
@ -198,7 +198,7 @@ default: DENY
|
||||
|
||||
=cut
|
||||
|
||||
sub addr_bad_dest_syntax { shift->_dsn(shift,shift,DENY,1,3); }
|
||||
sub addr_bad_dest_syntax { shift->_dsn(shift, shift, DENY, 1, 3); }
|
||||
|
||||
=item addr_dest_ambigous
|
||||
|
||||
@ -207,7 +207,7 @@ default: DENYSOFT
|
||||
|
||||
=cut
|
||||
|
||||
sub addr_dest_ambigous { shift->_dsn(shift,shift,DENYSOFT,1,4); }
|
||||
sub addr_dest_ambigous { shift->_dsn(shift, shift, DENYSOFT, 1, 4); }
|
||||
|
||||
=item addr_rcpt_ok
|
||||
|
||||
@ -217,7 +217,7 @@ default: OK
|
||||
=cut
|
||||
|
||||
# XXX: do we need this? Maybe in all address verifying plugins?
|
||||
sub addr_rcpt_ok { shift->_dsn(shift,shift,OK,1,5); }
|
||||
sub addr_rcpt_ok { shift->_dsn(shift, shift, OK, 1, 5); }
|
||||
|
||||
=item addr_mbox_moved
|
||||
|
||||
@ -226,7 +226,7 @@ default: DENY
|
||||
|
||||
=cut
|
||||
|
||||
sub addr_mbox_moved { shift->_dsn(shift,shift,DENY,1,6); }
|
||||
sub addr_mbox_moved { shift->_dsn(shift, shift, DENY, 1, 6); }
|
||||
|
||||
=item addr_bad_from_syntax
|
||||
|
||||
@ -235,7 +235,7 @@ default: DENY
|
||||
|
||||
=cut
|
||||
|
||||
sub addr_bad_from_syntax { shift->_dsn(shift,shift,DENY,1,7); }
|
||||
sub addr_bad_from_syntax { shift->_dsn(shift, shift, DENY, 1, 7); }
|
||||
|
||||
=item addr_bad_from_system
|
||||
|
||||
@ -246,7 +246,7 @@ default: DENY
|
||||
|
||||
=cut
|
||||
|
||||
sub addr_bad_from_system { shift->_dsn(shift,shift,DENY,1,8); }
|
||||
sub addr_bad_from_system { shift->_dsn(shift, shift, DENY, 1, 8); }
|
||||
|
||||
=head1 MAILBOX STATUS
|
||||
|
||||
@ -259,7 +259,7 @@ default: DENYSOFT
|
||||
|
||||
=cut
|
||||
|
||||
sub mbox_unspecified { shift->_dsn(shift,shift,DENYSOFT,2,0); }
|
||||
sub mbox_unspecified { shift->_dsn(shift, shift, DENYSOFT, 2, 0); }
|
||||
|
||||
=item mbox_disabled
|
||||
|
||||
@ -272,7 +272,7 @@ default: DENY ...but RFC says:
|
||||
|
||||
=cut
|
||||
|
||||
sub mbox_disabled { shift->_dsn(shift,shift,DENY,2,1); }
|
||||
sub mbox_disabled { shift->_dsn(shift, shift, DENY, 2, 1); }
|
||||
|
||||
=item mbox_full
|
||||
|
||||
@ -281,7 +281,7 @@ default: DENYSOFT
|
||||
|
||||
=cut
|
||||
|
||||
sub mbox_full { shift->_dsn(shift,shift,DENYSOFT,2,2); }
|
||||
sub mbox_full { shift->_dsn(shift, shift, DENYSOFT, 2, 2); }
|
||||
|
||||
=item mbox_msg_too_long
|
||||
|
||||
@ -290,7 +290,7 @@ default: DENY
|
||||
|
||||
=cut
|
||||
|
||||
sub mbox_msg_too_long { shift->_dsn(shift,shift,DENY,2,3); }
|
||||
sub mbox_msg_too_long { shift->_dsn(shift, shift, DENY, 2, 3); }
|
||||
|
||||
=item mbox_list_expansion_problem
|
||||
|
||||
@ -301,7 +301,7 @@ default: DENYSOFT
|
||||
|
||||
=cut
|
||||
|
||||
sub mbox_list_expansion_problem { shift->_dsn(shift,shift,DENYSOFT,2,4); }
|
||||
sub mbox_list_expansion_problem { shift->_dsn(shift, shift, DENYSOFT, 2, 4); }
|
||||
|
||||
=head1 MAIL SYSTEM STATUS
|
||||
|
||||
@ -314,7 +314,7 @@ default: DENYSOFT
|
||||
|
||||
=cut
|
||||
|
||||
sub sys_unspecified { shift->_dsn(shift,shift,DENYSOFT,3,0); }
|
||||
sub sys_unspecified { shift->_dsn(shift, shift, DENYSOFT, 3, 0); }
|
||||
|
||||
=item sys_disk_full
|
||||
|
||||
@ -323,7 +323,7 @@ default: DENYSOFT
|
||||
|
||||
=cut
|
||||
|
||||
sub sys_disk_full { shift->_dsn(shift,shift,DENYSOFT,3,1); }
|
||||
sub sys_disk_full { shift->_dsn(shift, shift, DENYSOFT, 3, 1); }
|
||||
|
||||
=item sys_not_accepting_mail
|
||||
|
||||
@ -332,7 +332,7 @@ default: DENYSOFT
|
||||
|
||||
=cut
|
||||
|
||||
sub sys_not_accepting_mail { shift->_dsn(shift,shift,DENYSOFT,3,2); }
|
||||
sub sys_not_accepting_mail { shift->_dsn(shift, shift, DENYSOFT, 3, 2); }
|
||||
|
||||
=item sys_not_supported
|
||||
|
||||
@ -345,7 +345,7 @@ default: DENYSOFT
|
||||
|
||||
=cut
|
||||
|
||||
sub sys_not_supported { shift->_dsn(shift,shift,DENYSOFT,3,3); }
|
||||
sub sys_not_supported { shift->_dsn(shift, shift, DENYSOFT, 3, 3); }
|
||||
|
||||
=item sys_msg_too_big
|
||||
|
||||
@ -356,7 +356,7 @@ default DENY
|
||||
|
||||
=cut
|
||||
|
||||
sub sys_msg_too_big { shift->_dsn(shift,shift,DENY,3,4); }
|
||||
sub sys_msg_too_big { shift->_dsn(shift, shift, DENY, 3, 4); }
|
||||
|
||||
=head1 NETWORK AND ROUTING STATUS
|
||||
|
||||
@ -371,7 +371,7 @@ default: DENYSOFT
|
||||
|
||||
=cut
|
||||
|
||||
sub net_unspecified { shift->_dsn(shift,shift,DENYSOFT,4,0); }
|
||||
sub net_unspecified { shift->_dsn(shift, shift, DENYSOFT, 4, 0); }
|
||||
|
||||
# not useful # sub net_no_answer { shift->_dsn(shift,shift,4,1); }
|
||||
# not useful # sub net_bad_connection { shift->_dsn(shift,shift,4,2); }
|
||||
@ -384,11 +384,10 @@ default: DENYSOFT
|
||||
=cut
|
||||
|
||||
sub temp_resolver_failed {
|
||||
shift->_dsn(shift,
|
||||
(shift || "Temporary address resolution failure"),
|
||||
DENYSOFT,4,3);
|
||||
shift->_dsn(shift, (shift || "Temporary address resolution failure"),
|
||||
DENYSOFT, 4, 3);
|
||||
}
|
||||
sub net_directory_server_failed { shift->_dsn(shift,shift,DENYSOFT,4,3); }
|
||||
sub net_directory_server_failed { shift->_dsn(shift, shift, DENYSOFT, 4, 3); }
|
||||
|
||||
# not useful # sub net_unable_to_route { shift->_dsn(shift,shift,4,4); }
|
||||
|
||||
@ -399,7 +398,7 @@ default: DENYSOFT
|
||||
|
||||
=cut
|
||||
|
||||
sub net_system_congested { shift->_dsn(shift,shift,DENYSOFT,4,5); }
|
||||
sub net_system_congested { shift->_dsn(shift, shift, DENYSOFT, 4, 5); }
|
||||
|
||||
=item net_routing_loop, too_many_hops
|
||||
|
||||
@ -416,8 +415,11 @@ Why do we want to DENYSOFT something like this?
|
||||
|
||||
=cut
|
||||
|
||||
sub net_routing_loop { shift->_dsn(shift,shift,DENY,4,6); }
|
||||
sub too_many_hops { shift->_dsn(shift,(shift || "Too many hops"),DENY,4,6,); }
|
||||
sub net_routing_loop { shift->_dsn(shift, shift, DENY, 4, 6); }
|
||||
sub too_many_hops {
|
||||
shift->_dsn(shift, (shift || "Too many hops"), DENY, 4, 6,);
|
||||
}
|
||||
|
||||
# not useful # sub delivery_time_expired { shift->_dsn(shift,shift,4,7); }
|
||||
|
||||
=head1 MAIL DELIVERY PROTOCOL STATUS
|
||||
@ -431,7 +433,7 @@ default: DENYSOFT
|
||||
|
||||
=cut
|
||||
|
||||
sub proto_unspecified { shift->_dsn(shift,shift,DENYSOFT,5,0); }
|
||||
sub proto_unspecified { shift->_dsn(shift, shift, DENYSOFT, 5, 0); }
|
||||
|
||||
=item proto_invalid_command
|
||||
|
||||
@ -440,7 +442,7 @@ default: DENY
|
||||
|
||||
=cut
|
||||
|
||||
sub proto_invalid_command { shift->_dsn(shift,shift,DENY,5,1); }
|
||||
sub proto_invalid_command { shift->_dsn(shift, shift, DENY, 5, 1); }
|
||||
|
||||
=item proto_syntax_error
|
||||
|
||||
@ -449,7 +451,7 @@ default: DENY
|
||||
|
||||
=cut
|
||||
|
||||
sub proto_syntax_error { shift->_dsn(shift,shift,DENY,5,2); }
|
||||
sub proto_syntax_error { shift->_dsn(shift, shift, DENY, 5, 2); }
|
||||
|
||||
=item proto_rcpt_list_too_long, too_many_rcpts
|
||||
|
||||
@ -458,8 +460,8 @@ default: DENYSOFT
|
||||
|
||||
=cut
|
||||
|
||||
sub proto_rcpt_list_too_long { shift->_dsn(shift,shift,DENYSOFT,5,3); }
|
||||
sub too_many_rcpts { shift->_dsn(shift,shift,DENYSOFT,5,3); }
|
||||
sub proto_rcpt_list_too_long { shift->_dsn(shift, shift, DENYSOFT, 5, 3); }
|
||||
sub too_many_rcpts { shift->_dsn(shift, shift, DENYSOFT, 5, 3); }
|
||||
|
||||
=item proto_invalid_cmd_args
|
||||
|
||||
@ -468,7 +470,7 @@ default: DENY
|
||||
|
||||
=cut
|
||||
|
||||
sub proto_invalid_cmd_args { shift->_dsn(shift,shift,DENY,5,4); }
|
||||
sub proto_invalid_cmd_args { shift->_dsn(shift, shift, DENY, 5, 4); }
|
||||
|
||||
=item proto_wrong_version
|
||||
|
||||
@ -479,7 +481,7 @@ default: DENYSOFT
|
||||
|
||||
=cut
|
||||
|
||||
sub proto_wrong_version { shift->_dsn(shift,shift,DENYSOFT,5,5); }
|
||||
sub proto_wrong_version { shift->_dsn(shift, shift, DENYSOFT, 5, 5); }
|
||||
|
||||
=head1 MESSAGE CONTENT OR MESSAGE MEDIA STATUS
|
||||
|
||||
@ -492,7 +494,7 @@ default: DENYSOFT
|
||||
|
||||
=cut
|
||||
|
||||
sub media_unspecified { shift->_dsn(shift,shift,DENYSOFT,6,0); }
|
||||
sub media_unspecified { shift->_dsn(shift, shift, DENYSOFT, 6, 0); }
|
||||
|
||||
=item media_unsupported
|
||||
|
||||
@ -501,7 +503,7 @@ default: DENY
|
||||
|
||||
=cut
|
||||
|
||||
sub media_unsupported { shift->_dsn(shift,shift,DENY,6,1); }
|
||||
sub media_unsupported { shift->_dsn(shift, shift, DENY, 6, 1); }
|
||||
|
||||
=item media_conv_prohibited
|
||||
|
||||
@ -510,7 +512,7 @@ default: DENY
|
||||
|
||||
=cut
|
||||
|
||||
sub media_conv_prohibited { shift->_dsn(shift,shift,DENY,6,2); }
|
||||
sub media_conv_prohibited { shift->_dsn(shift, shift, DENY, 6, 2); }
|
||||
|
||||
=item media_conv_unsupported
|
||||
|
||||
@ -519,7 +521,7 @@ default: DENYSOFT
|
||||
|
||||
=cut
|
||||
|
||||
sub media_conv_unsupported { shift->_dsn(shift,shift,DENYSOFT,6,3); }
|
||||
sub media_conv_unsupported { shift->_dsn(shift, shift, DENYSOFT, 6, 3); }
|
||||
|
||||
=item media_conv_lossy
|
||||
|
||||
@ -530,7 +532,7 @@ default: DENYSOFT
|
||||
|
||||
=cut
|
||||
|
||||
sub media_conv_lossy { shift->_dsn(shift,shift,DENYSOFT,6,4); }
|
||||
sub media_conv_lossy { shift->_dsn(shift, shift, DENYSOFT, 6, 4); }
|
||||
|
||||
=head1 SECURITY OR POLICY STATUS
|
||||
|
||||
@ -543,7 +545,7 @@ default: DENYSOFT
|
||||
|
||||
=cut
|
||||
|
||||
sub sec_unspecified { shift->_dsn(shift,shift,DENYSOFT,7,0); }
|
||||
sub sec_unspecified { shift->_dsn(shift, shift, DENYSOFT, 7, 0); }
|
||||
|
||||
=item sec_sender_unauthorized, bad_sender_ip, relaying_denied
|
||||
|
||||
@ -552,12 +554,14 @@ default: DENY
|
||||
|
||||
=cut
|
||||
|
||||
sub sec_sender_unauthorized { shift->_dsn(shift,shift,DENY,7,1); }
|
||||
sub sec_sender_unauthorized { shift->_dsn(shift, shift, DENY, 7, 1); }
|
||||
|
||||
sub bad_sender_ip {
|
||||
shift->_dsn(shift,(shift || "Bad sender's IP"),DENY,7,1,);
|
||||
shift->_dsn(shift, (shift || "Bad sender's IP"), DENY, 7, 1,);
|
||||
}
|
||||
|
||||
sub relaying_denied {
|
||||
shift->_dsn(shift,(shift || "Relaying denied"),DENY,7,1);
|
||||
shift->_dsn(shift, (shift || "Relaying denied"), DENY, 7, 1);
|
||||
}
|
||||
|
||||
=item sec_list_dest_prohibited
|
||||
@ -567,7 +571,7 @@ default: DENY
|
||||
|
||||
=cut
|
||||
|
||||
sub sec_list_dest_prohibited { shift->_dsn(shift,shift,DENY,7,2); }
|
||||
sub sec_list_dest_prohibited { shift->_dsn(shift, shift, DENY, 7, 2); }
|
||||
|
||||
=item sec_conv_failed
|
||||
|
||||
@ -576,7 +580,7 @@ default: DENY
|
||||
|
||||
=cut
|
||||
|
||||
sub sec_conv_failed { shift->_dsn(shift,shift,DENY,7,3); }
|
||||
sub sec_conv_failed { shift->_dsn(shift, shift, DENY, 7, 3); }
|
||||
|
||||
=item sec_feature_unsupported
|
||||
|
||||
@ -585,7 +589,7 @@ default: DENY
|
||||
|
||||
=cut
|
||||
|
||||
sub sec_feature_unsupported { shift->_dsn(shift,shift,DENY,7,4); }
|
||||
sub sec_feature_unsupported { shift->_dsn(shift, shift, DENY, 7, 4); }
|
||||
|
||||
=item sec_crypto_failure
|
||||
|
||||
@ -594,7 +598,7 @@ default: DENY
|
||||
|
||||
=cut
|
||||
|
||||
sub sec_crypto_failure { shift->_dsn(shift,shift,DENY,7,5); }
|
||||
sub sec_crypto_failure { shift->_dsn(shift, shift, DENY, 7, 5); }
|
||||
|
||||
=item sec_crypto_algorithm_unsupported
|
||||
|
||||
@ -603,7 +607,9 @@ default: DENYSOFT
|
||||
|
||||
=cut
|
||||
|
||||
sub sec_crypto_algorithm_unsupported { shift->_dsn(shift,shift,DENYSOFT,7,6); }
|
||||
sub sec_crypto_algorithm_unsupported {
|
||||
shift->_dsn(shift, shift, DENYSOFT, 7, 6);
|
||||
}
|
||||
|
||||
=item sec_msg_integrity_failure
|
||||
|
||||
@ -614,7 +620,7 @@ default: DENY
|
||||
|
||||
=cut
|
||||
|
||||
sub sec_msg_integrity_failure { shift->_dsn(shift,shift,DENY,7,7); }
|
||||
sub sec_msg_integrity_failure { shift->_dsn(shift, shift, DENY, 7, 7); }
|
||||
|
||||
1;
|
||||
|
||||
|
@ -9,102 +9,107 @@ use Qpsmtpd::Constants;
|
||||
|
||||
# more or less in the order they will fire
|
||||
our @hooks = qw(
|
||||
logging config post-fork pre-connection connect ehlo_parse ehlo
|
||||
helo_parse helo auth_parse auth auth-plain auth-login auth-cram-md5
|
||||
rcpt_parse rcpt_pre rcpt mail_parse mail mail_pre
|
||||
data data_headers_end data_post queue_pre queue queue_post vrfy noop
|
||||
quit reset_transaction disconnect post-connection
|
||||
unrecognized_command deny ok received_line help
|
||||
);
|
||||
logging config post-fork pre-connection connect ehlo_parse ehlo
|
||||
helo_parse helo auth_parse auth auth-plain auth-login auth-cram-md5
|
||||
rcpt_parse rcpt_pre rcpt mail_parse mail mail_pre
|
||||
data data_headers_end data_post queue_pre queue queue_post vrfy noop
|
||||
quit reset_transaction disconnect post-connection
|
||||
unrecognized_command deny ok received_line help
|
||||
);
|
||||
our %hooks = map { $_ => 1 } @hooks;
|
||||
|
||||
sub new {
|
||||
my $proto = shift;
|
||||
my $class = ref($proto) || $proto;
|
||||
bless ({}, $class);
|
||||
my $proto = shift;
|
||||
my $class = ref($proto) || $proto;
|
||||
bless({}, $class);
|
||||
}
|
||||
|
||||
sub hook_name {
|
||||
return shift->{_hook};
|
||||
return shift->{_hook};
|
||||
}
|
||||
|
||||
sub register_hook {
|
||||
my ($plugin, $hook, $method, $unshift) = @_;
|
||||
my ($plugin, $hook, $method, $unshift) = @_;
|
||||
|
||||
die $plugin->plugin_name . " : Invalid hook: $hook" unless $hooks{$hook};
|
||||
die $plugin->plugin_name . " : Invalid hook: $hook" unless $hooks{$hook};
|
||||
|
||||
$plugin->{_qp}->log(LOGDEBUG, $plugin->plugin_name, "hooking", $hook)
|
||||
unless $hook =~ /logging/; # can't log during load_logging()
|
||||
$plugin->{_qp}->log(LOGDEBUG, $plugin->plugin_name, "hooking", $hook)
|
||||
unless $hook =~ /logging/; # can't log during load_logging()
|
||||
|
||||
# I can't quite decide if it's better to parse this code ref or if
|
||||
# we should pass the plugin object and method name ... hmn.
|
||||
$plugin->qp->_register_hook
|
||||
($hook,
|
||||
{ code => sub { local $plugin->{_qp} = shift;
|
||||
local $plugin->{_hook} = $hook;
|
||||
$plugin->$method(@_)
|
||||
},
|
||||
name => $plugin->plugin_name,
|
||||
},
|
||||
$unshift,
|
||||
);
|
||||
# I can't quite decide if it's better to parse this code ref or if
|
||||
# we should pass the plugin object and method name ... hmn.
|
||||
$plugin->qp->_register_hook(
|
||||
$hook,
|
||||
{
|
||||
code => sub {
|
||||
local $plugin->{_qp} = shift;
|
||||
local $plugin->{_hook} = $hook;
|
||||
$plugin->$method(@_);
|
||||
},
|
||||
name => $plugin->plugin_name,
|
||||
},
|
||||
$unshift,
|
||||
);
|
||||
}
|
||||
|
||||
sub _register {
|
||||
my $self = shift;
|
||||
my $qp = shift;
|
||||
local $self->{_qp} = $qp;
|
||||
$self->init($qp, @_) if $self->can('init');
|
||||
$self->_register_standard_hooks($qp, @_);
|
||||
$self->register($qp, @_) if $self->can('register');
|
||||
my $self = shift;
|
||||
my $qp = shift;
|
||||
local $self->{_qp} = $qp;
|
||||
$self->init($qp, @_) if $self->can('init');
|
||||
$self->_register_standard_hooks($qp, @_);
|
||||
$self->register($qp, @_) if $self->can('register');
|
||||
}
|
||||
|
||||
sub qp {
|
||||
shift->{_qp};
|
||||
shift->{_qp};
|
||||
}
|
||||
|
||||
sub log {
|
||||
my $self = shift;
|
||||
return if defined $self->{_hook} && $self->{_hook} eq 'logging';
|
||||
my $level = $self->adjust_log_level( shift, $self->plugin_name );
|
||||
$self->{_qp}->varlog($level, $self->{_hook}, $self->plugin_name, @_);
|
||||
my $self = shift;
|
||||
return if defined $self->{_hook} && $self->{_hook} eq 'logging';
|
||||
my $level = $self->adjust_log_level(shift, $self->plugin_name);
|
||||
$self->{_qp}->varlog($level, $self->{_hook}, $self->plugin_name, @_);
|
||||
}
|
||||
|
||||
sub adjust_log_level {
|
||||
my ( $self, $cur_level, $plugin_name) = @_;
|
||||
my ($self, $cur_level, $plugin_name) = @_;
|
||||
|
||||
my $adj = $self->{_args}{loglevel} or return $cur_level;
|
||||
|
||||
return $adj if $adj =~ m/^[01234567]$/; # a raw syslog numeral
|
||||
return $adj if $adj =~ m/^[01234567]$/; # a raw syslog numeral
|
||||
|
||||
if ( $adj !~ /^[\+\-][\d]$/ ) {
|
||||
$self->log( LOGERROR, $self-"invalid $plugin_name loglevel setting ($adj)" );
|
||||
undef $self->{_args}{loglevel}; # only complain once per plugin
|
||||
if ($adj !~ /^[\+\-][\d]$/) {
|
||||
$self->log(LOGERROR,
|
||||
$self - "invalid $plugin_name loglevel setting ($adj)");
|
||||
undef $self->{_args}{loglevel}; # only complain once per plugin
|
||||
return $cur_level;
|
||||
};
|
||||
}
|
||||
|
||||
my $operator = substr($adj, 0, 1);
|
||||
my $adjust = substr($adj, -1, 1);
|
||||
my $operator = substr($adj, 0, 1);
|
||||
my $adjust = substr($adj, -1, 1);
|
||||
|
||||
my $new_level = $operator eq '+' ? $cur_level + $adjust : $cur_level - $adjust;
|
||||
my $new_level =
|
||||
$operator eq '+' ? $cur_level + $adjust : $cur_level - $adjust;
|
||||
|
||||
$new_level = 7 if $new_level > 7;
|
||||
$new_level = 0 if $new_level < 0;
|
||||
|
||||
return $new_level;
|
||||
};
|
||||
}
|
||||
|
||||
sub transaction {
|
||||
# not sure if this will work in a non-forking or a threaded daemon
|
||||
shift->qp->transaction;
|
||||
|
||||
# not sure if this will work in a non-forking or a threaded daemon
|
||||
shift->qp->transaction;
|
||||
}
|
||||
|
||||
sub connection {
|
||||
shift->qp->connection;
|
||||
shift->qp->connection;
|
||||
}
|
||||
|
||||
sub spool_dir {
|
||||
shift->qp->spool_dir;
|
||||
shift->qp->spool_dir;
|
||||
}
|
||||
|
||||
sub auth_user {
|
||||
@ -116,17 +121,17 @@ sub auth_mechanism {
|
||||
}
|
||||
|
||||
sub temp_file {
|
||||
my $self = shift;
|
||||
my $tempfile = $self->qp->temp_file;
|
||||
push @{$self->qp->transaction->{_temp_files}}, $tempfile;
|
||||
return $tempfile;
|
||||
my $self = shift;
|
||||
my $tempfile = $self->qp->temp_file;
|
||||
push @{$self->qp->transaction->{_temp_files}}, $tempfile;
|
||||
return $tempfile;
|
||||
}
|
||||
|
||||
sub temp_dir {
|
||||
my $self = shift;
|
||||
my $tempdir = $self->qp->temp_dir();
|
||||
push @{$self->qp->transaction->{_temp_dirs}}, $tempdir;
|
||||
return $tempdir;
|
||||
my $self = shift;
|
||||
my $tempdir = $self->qp->temp_dir();
|
||||
push @{$self->qp->transaction->{_temp_dirs}}, $tempdir;
|
||||
return $tempdir;
|
||||
}
|
||||
|
||||
# plugin inheritance:
|
||||
@ -137,32 +142,31 @@ sub temp_dir {
|
||||
# $self->SUPER::register(@_);
|
||||
# }
|
||||
sub isa_plugin {
|
||||
my ($self, $parent) = @_;
|
||||
my ($currentPackage) = caller;
|
||||
my ($self, $parent) = @_;
|
||||
my ($currentPackage) = caller;
|
||||
|
||||
my $cleanParent = $parent;
|
||||
$cleanParent =~ s/\W/_/g;
|
||||
my $newPackage = $currentPackage."::_isa_$cleanParent";
|
||||
my $cleanParent = $parent;
|
||||
$cleanParent =~ s/\W/_/g;
|
||||
my $newPackage = $currentPackage . "::_isa_$cleanParent";
|
||||
|
||||
# don't reload plugins if they are already loaded
|
||||
return if defined &{"${newPackage}::plugin_name"};
|
||||
# don't reload plugins if they are already loaded
|
||||
return if defined &{"${newPackage}::plugin_name"};
|
||||
|
||||
# find $parent in plugin_dirs
|
||||
my $parent_dir;
|
||||
for ($self->qp->plugin_dirs) {
|
||||
if (-e "$_/$parent") {
|
||||
$parent_dir = $_;
|
||||
last;
|
||||
# find $parent in plugin_dirs
|
||||
my $parent_dir;
|
||||
for ($self->qp->plugin_dirs) {
|
||||
if (-e "$_/$parent") {
|
||||
$parent_dir = $_;
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
die "cannot find plugin '$parent'" unless $parent_dir;
|
||||
die "cannot find plugin '$parent'" unless $parent_dir;
|
||||
|
||||
$self->compile($self->plugin_name . "_isa_$cleanParent",
|
||||
$newPackage,
|
||||
"$parent_dir/$parent");
|
||||
warn "---- $newPackage\n";
|
||||
no strict 'refs';
|
||||
push @{"${currentPackage}::ISA"}, $newPackage;
|
||||
$self->compile($self->plugin_name . "_isa_$cleanParent",
|
||||
$newPackage, "$parent_dir/$parent");
|
||||
warn "---- $newPackage\n";
|
||||
no strict 'refs';
|
||||
push @{"${currentPackage}::ISA"}, $newPackage;
|
||||
}
|
||||
|
||||
# why isn't compile private? it's only called from Plugin and Qpsmtpd.
|
||||
@ -172,8 +176,8 @@ sub compile {
|
||||
my $sub;
|
||||
open F, $file or die "could not open $file: $!";
|
||||
{
|
||||
local $/ = undef;
|
||||
$sub = <F>;
|
||||
local $/ = undef;
|
||||
$sub = <F>;
|
||||
}
|
||||
close F;
|
||||
|
||||
@ -189,19 +193,19 @@ sub compile {
|
||||
}
|
||||
|
||||
my $eval = join(
|
||||
"\n",
|
||||
"package $package;",
|
||||
'use Qpsmtpd::Constants;',
|
||||
"require Qpsmtpd::Plugin;",
|
||||
'use vars qw(@ISA);',
|
||||
'use strict;',
|
||||
'@ISA = qw(Qpsmtpd::Plugin);',
|
||||
($test_mode ? 'use Test::More;' : ''),
|
||||
"sub plugin_name { qq[$plugin] }",
|
||||
$line,
|
||||
$sub,
|
||||
"\n", # last line comment without newline?
|
||||
);
|
||||
"\n",
|
||||
"package $package;",
|
||||
'use Qpsmtpd::Constants;',
|
||||
"require Qpsmtpd::Plugin;",
|
||||
'use vars qw(@ISA);',
|
||||
'use strict;',
|
||||
'@ISA = qw(Qpsmtpd::Plugin);',
|
||||
($test_mode ? 'use Test::More;' : ''),
|
||||
"sub plugin_name { qq[$plugin] }",
|
||||
$line,
|
||||
$sub,
|
||||
"\n", # last line comment without newline?
|
||||
);
|
||||
|
||||
#warn "eval: $eval";
|
||||
|
||||
@ -213,120 +217,126 @@ sub compile {
|
||||
}
|
||||
|
||||
sub get_reject {
|
||||
my $self = shift;
|
||||
my $self = shift;
|
||||
my $smtp_mess = shift || "why didn't you pass an error message?";
|
||||
my $log_mess = shift || '';
|
||||
my $log_mess = shift || '';
|
||||
$log_mess = ", $log_mess" if $log_mess;
|
||||
|
||||
my $reject = $self->{_args}{reject};
|
||||
if ( defined $reject && ! $reject ) {
|
||||
if (defined $reject && !$reject) {
|
||||
$self->log(LOGINFO, "fail, reject disabled" . $log_mess);
|
||||
return DECLINED;
|
||||
};
|
||||
}
|
||||
|
||||
# the naughty plugin will reject later
|
||||
if ( $reject eq 'naughty' ) {
|
||||
if ($reject eq 'naughty') {
|
||||
$self->log(LOGINFO, "fail, NAUGHTY" . $log_mess);
|
||||
return $self->store_deferred_reject( $smtp_mess );
|
||||
};
|
||||
return $self->store_deferred_reject($smtp_mess);
|
||||
}
|
||||
|
||||
# they asked for reject, we give them reject
|
||||
$self->log(LOGINFO, "fail" . $log_mess);
|
||||
return ( $self->get_reject_type(), $smtp_mess);
|
||||
};
|
||||
return ($self->get_reject_type(), $smtp_mess);
|
||||
}
|
||||
|
||||
sub get_reject_type {
|
||||
my $self = shift;
|
||||
my $self = shift;
|
||||
my $default = shift || DENY;
|
||||
my $deny = shift || $self->{_args}{reject_type} or return $default;
|
||||
my $deny = shift || $self->{_args}{reject_type} or return $default;
|
||||
|
||||
return $deny =~ /^(temp|soft)$/i ? DENYSOFT
|
||||
: $deny =~ /^(perm|hard)$/i ? DENY
|
||||
: $deny eq 'disconnect' ? DENY_DISCONNECT
|
||||
: $deny eq 'temp_disconnect' ? DENYSOFT_DISCONNECT
|
||||
: $default;
|
||||
};
|
||||
return
|
||||
$deny =~ /^(temp|soft)$/i ? DENYSOFT
|
||||
: $deny =~ /^(perm|hard)$/i ? DENY
|
||||
: $deny eq 'disconnect' ? DENY_DISCONNECT
|
||||
: $deny eq 'temp_disconnect' ? DENYSOFT_DISCONNECT
|
||||
: $default;
|
||||
}
|
||||
|
||||
sub store_deferred_reject {
|
||||
my ($self, $smtp_mess) = @_;
|
||||
|
||||
# store the reject message that the naughty plugin will return later
|
||||
if ( ! $self->connection->notes('naughty') ) {
|
||||
if (!$self->connection->notes('naughty')) {
|
||||
$self->connection->notes('naughty', $smtp_mess);
|
||||
}
|
||||
else {
|
||||
# append this reject message to the message
|
||||
my $prev = $self->connection->notes('naughty');
|
||||
$self->connection->notes('naughty', "$prev\015\012$smtp_mess");
|
||||
};
|
||||
if ( ! $self->connection->notes('naughty_reject_type') ) {
|
||||
$self->connection->notes('naughty_reject_type', $self->{_args}{reject_type} );
|
||||
}
|
||||
if (!$self->connection->notes('naughty_reject_type')) {
|
||||
$self->connection->notes('naughty_reject_type',
|
||||
$self->{_args}{reject_type});
|
||||
}
|
||||
return (DECLINED);
|
||||
};
|
||||
}
|
||||
|
||||
sub init_resolver {
|
||||
my $self = shift;
|
||||
return $self->{_resolver} if $self->{_resolver};
|
||||
$self->log( LOGDEBUG, "initializing Net::DNS::Resolver");
|
||||
$self->log(LOGDEBUG, "initializing Net::DNS::Resolver");
|
||||
$self->{_resolver} = Net::DNS::Resolver->new(dnsrch => 0);
|
||||
my $timeout = $self->{_args}{dns_timeout} || 5;
|
||||
$self->{_resolver}->tcp_timeout($timeout);
|
||||
$self->{_resolver}->udp_timeout($timeout);
|
||||
return $self->{_resolver};
|
||||
};
|
||||
}
|
||||
|
||||
sub is_immune {
|
||||
my $self = shift;
|
||||
|
||||
if ( $self->qp->connection->relay_client() ) {
|
||||
if ($self->qp->connection->relay_client()) {
|
||||
|
||||
# set by plugins/relay, or Qpsmtpd::Auth
|
||||
$self->log(LOGINFO, "skip, relay client");
|
||||
return 1;
|
||||
};
|
||||
if ( $self->qp->connection->notes('whitelisthost') ) {
|
||||
}
|
||||
if ($self->qp->connection->notes('whitelisthost')) {
|
||||
|
||||
# set by plugins/dns_whitelist_soft or plugins/whitelist
|
||||
$self->log(LOGINFO, "skip, whitelisted host");
|
||||
return 1;
|
||||
};
|
||||
if ( $self->qp->transaction->notes('whitelistsender') ) {
|
||||
}
|
||||
if ($self->qp->transaction->notes('whitelistsender')) {
|
||||
|
||||
# set by plugins/whitelist
|
||||
$self->log(LOGINFO, "skip, whitelisted sender");
|
||||
return 1;
|
||||
};
|
||||
if ( $self->connection->notes('naughty') ) {
|
||||
}
|
||||
if ($self->connection->notes('naughty')) {
|
||||
|
||||
# see plugins/naughty
|
||||
$self->log(LOGINFO, "skip, naughty");
|
||||
return 1;
|
||||
};
|
||||
if ( $self->connection->notes('rejected') ) {
|
||||
}
|
||||
if ($self->connection->notes('rejected')) {
|
||||
|
||||
# http://www.steve.org.uk/Software/ms-lite/
|
||||
$self->log(LOGINFO, "skip, already rejected");
|
||||
return 1;
|
||||
};
|
||||
}
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
sub adjust_karma {
|
||||
my ( $self, $value ) = @_;
|
||||
my ($self, $value) = @_;
|
||||
|
||||
my $karma = $self->connection->notes('karma') || 0;
|
||||
$karma += $value;
|
||||
$self->log(LOGDEBUG, "karma adjust: $value ($karma)");
|
||||
$self->connection->notes('karma', $karma);
|
||||
return $value;
|
||||
};
|
||||
|
||||
sub _register_standard_hooks {
|
||||
my ($plugin, $qp) = @_;
|
||||
|
||||
for my $hook (@hooks) {
|
||||
my $hooksub = "hook_$hook";
|
||||
$hooksub =~ s/\W/_/g;
|
||||
$plugin->register_hook( $hook, $hooksub )
|
||||
if ($plugin->can($hooksub));
|
||||
}
|
||||
}
|
||||
|
||||
sub _register_standard_hooks {
|
||||
my ($plugin, $qp) = @_;
|
||||
|
||||
for my $hook (@hooks) {
|
||||
my $hooksub = "hook_$hook";
|
||||
$hooksub =~ s/\W/_/g;
|
||||
$plugin->register_hook($hook, $hooksub)
|
||||
if ($plugin->can($hooksub));
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
@ -1,32 +1,33 @@
|
||||
package Qpsmtpd::PollServer;
|
||||
|
||||
use base ('Danga::Client', 'Qpsmtpd::SMTP');
|
||||
|
||||
# use fields required to be a subclass of Danga::Client. Have to include
|
||||
# all fields used by Qpsmtpd.pm here too.
|
||||
use fields qw(
|
||||
input_sock
|
||||
mode
|
||||
header_lines
|
||||
in_header
|
||||
data_size
|
||||
max_size
|
||||
hooks
|
||||
start_time
|
||||
cmd_timeout
|
||||
conn
|
||||
_auth
|
||||
_auth_mechanism
|
||||
_auth_state
|
||||
_auth_ticket
|
||||
_auth_user
|
||||
_commands
|
||||
_config_cache
|
||||
_connection
|
||||
_continuation
|
||||
_extras
|
||||
_test_mode
|
||||
_transaction
|
||||
);
|
||||
input_sock
|
||||
mode
|
||||
header_lines
|
||||
in_header
|
||||
data_size
|
||||
max_size
|
||||
hooks
|
||||
start_time
|
||||
cmd_timeout
|
||||
conn
|
||||
_auth
|
||||
_auth_mechanism
|
||||
_auth_state
|
||||
_auth_ticket
|
||||
_auth_user
|
||||
_commands
|
||||
_config_cache
|
||||
_connection
|
||||
_continuation
|
||||
_extras
|
||||
_test_mode
|
||||
_transaction
|
||||
);
|
||||
use Qpsmtpd::Constants;
|
||||
use Qpsmtpd::Address;
|
||||
use ParaDNS;
|
||||
@ -36,7 +37,7 @@ use Socket qw(inet_aton AF_INET CRLF);
|
||||
use Time::HiRes qw(time);
|
||||
use strict;
|
||||
|
||||
sub max_idle_time { 60 }
|
||||
sub max_idle_time { 60 }
|
||||
sub max_connect_time { 1200 }
|
||||
|
||||
sub input_sock {
|
||||
@ -49,10 +50,10 @@ sub new {
|
||||
my Qpsmtpd::PollServer $self = shift;
|
||||
|
||||
$self = fields::new($self) unless ref $self;
|
||||
$self->SUPER::new( @_ );
|
||||
$self->SUPER::new(@_);
|
||||
$self->{cmd_timeout} = 5;
|
||||
$self->{start_time} = time;
|
||||
$self->{mode} = 'connect';
|
||||
$self->{start_time} = time;
|
||||
$self->{mode} = 'connect';
|
||||
$self->load_plugins;
|
||||
$self->load_logging;
|
||||
|
||||
@ -84,19 +85,19 @@ sub reset_for_next_message {
|
||||
$self->SUPER::reset_for_next_message(@_);
|
||||
|
||||
$self->{_commands} = {
|
||||
ehlo => 1,
|
||||
helo => 1,
|
||||
rset => 1,
|
||||
mail => 1,
|
||||
rcpt => 1,
|
||||
data => 1,
|
||||
help => 1,
|
||||
vrfy => 1,
|
||||
noop => 1,
|
||||
quit => 1,
|
||||
auth => 0, # disabled by default
|
||||
};
|
||||
$self->{mode} = 'cmd';
|
||||
ehlo => 1,
|
||||
helo => 1,
|
||||
rset => 1,
|
||||
mail => 1,
|
||||
rcpt => 1,
|
||||
data => 1,
|
||||
help => 1,
|
||||
vrfy => 1,
|
||||
noop => 1,
|
||||
quit => 1,
|
||||
auth => 0, # disabled by default
|
||||
};
|
||||
$self->{mode} = 'cmd';
|
||||
$self->{_extras} = {};
|
||||
}
|
||||
|
||||
@ -121,17 +122,18 @@ my %cmd_cache;
|
||||
sub process_line {
|
||||
my Qpsmtpd::PollServer $self = shift;
|
||||
my $line = shift || return;
|
||||
if ($::DEBUG > 1) { print "$$:".($self+0)."C($self->{mode}): $line"; }
|
||||
if ($::DEBUG > 1) { print "$$:" . ($self + 0) . "C($self->{mode}): $line"; }
|
||||
if ($self->{mode} eq 'cmd') {
|
||||
$line =~ s/\r?\n$//s;
|
||||
$self->connection->notes('original_string', $line);
|
||||
my ($cmd, @params) = split(/ +/, $line, 2);
|
||||
my $meth = lc($cmd);
|
||||
if (my $lookup = $cmd_cache{$meth} || $self->{_commands}->{$meth} && $self->can($meth)) {
|
||||
if (my $lookup =
|
||||
$cmd_cache{$meth}
|
||||
|| $self->{_commands}->{$meth} && $self->can($meth))
|
||||
{
|
||||
$cmd_cache{$meth} = $lookup;
|
||||
eval {
|
||||
$lookup->($self, @params);
|
||||
};
|
||||
eval { $lookup->($self, @params); };
|
||||
if ($@) {
|
||||
my $error = $@;
|
||||
chomp($error);
|
||||
@ -141,11 +143,13 @@ sub process_line {
|
||||
}
|
||||
else {
|
||||
# No such method - i.e. unrecognized command
|
||||
my ($rc, $msg) = $self->run_hooks("unrecognized_command", $meth, @params);
|
||||
my ($rc, $msg) =
|
||||
$self->run_hooks("unrecognized_command", $meth, @params);
|
||||
}
|
||||
}
|
||||
elsif ($self->{mode} eq 'connect') {
|
||||
$self->{mode} = 'cmd';
|
||||
|
||||
# I've removed an eval{} from around this. It shouldn't ever die()
|
||||
# but if it does we're a bit screwed... Ah well :-)
|
||||
$self->start_conversation;
|
||||
@ -173,22 +177,24 @@ sub start_conversation {
|
||||
my Qpsmtpd::PollServer $self = shift;
|
||||
|
||||
my $conn = $self->connection;
|
||||
|
||||
# set remote_host, remote_ip and remote_port
|
||||
my ($ip, $port) = split(/:/, $self->peer_addr_string);
|
||||
return $self->close() unless $ip;
|
||||
$conn->remote_ip($ip);
|
||||
$conn->remote_port($port);
|
||||
$conn->remote_info("[$ip]");
|
||||
my ($lip,$lport) = split(/:/, $self->local_addr_string);
|
||||
my ($lip, $lport) = split(/:/, $self->local_addr_string);
|
||||
$conn->local_ip($lip);
|
||||
$conn->local_port($lport);
|
||||
|
||||
ParaDNS->new(
|
||||
finished => sub { $self->continue_read(); $self->run_hooks("connect") },
|
||||
finished => sub { $self->continue_read(); $self->run_hooks("connect") },
|
||||
|
||||
# NB: Setting remote_info to the same as remote_host
|
||||
callback => sub { $conn->remote_info($conn->remote_host($_[0])) },
|
||||
host => $ip,
|
||||
);
|
||||
callback => sub { $conn->remote_info($conn->remote_host($_[0])) },
|
||||
host => $ip,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
@ -231,14 +237,16 @@ sub data_respond {
|
||||
return;
|
||||
}
|
||||
return $self->respond(503, "MAIL first") unless $self->transaction->sender;
|
||||
return $self->respond(503, "RCPT first") unless $self->transaction->recipients;
|
||||
return $self->respond(503, "RCPT first")
|
||||
unless $self->transaction->recipients;
|
||||
|
||||
$self->{header_lines} = '';
|
||||
$self->{data_size} = 0;
|
||||
$self->{in_header} = 1;
|
||||
$self->{max_size} = ($self->config('databytes'))[0] || 0;
|
||||
$self->{data_size} = 0;
|
||||
$self->{in_header} = 1;
|
||||
$self->{max_size} = ($self->config('databytes'))[0] || 0;
|
||||
|
||||
$self->log(LOGDEBUG, "max_size: $self->{max_size} / size: $self->{data_size}");
|
||||
$self->log(LOGDEBUG,
|
||||
"max_size: $self->{max_size} / size: $self->{data_size}");
|
||||
|
||||
$self->respond(354, "go ahead");
|
||||
|
||||
@ -255,10 +263,10 @@ sub got_data {
|
||||
my $remainder;
|
||||
if ($data =~ s/^\.\r\n(.*)\z//ms) {
|
||||
$remainder = $1;
|
||||
$done = 1;
|
||||
$done = 1;
|
||||
}
|
||||
|
||||
# add a transaction->blocked check back here when we have line by line plugin access...
|
||||
# add a transaction->blocked check back here when we have line by line plugin access...
|
||||
unless (($self->{max_size} and $self->{data_size} > $self->{max_size})) {
|
||||
$data =~ s/\r\n/\n/mg;
|
||||
$data =~ s/^\.\./\./mg;
|
||||
@ -268,24 +276,29 @@ sub got_data {
|
||||
|
||||
if ($self->{header_lines} =~ s/\n(\n.*)\z/\n/ms) {
|
||||
$data = $1;
|
||||
|
||||
# end of headers
|
||||
$self->{in_header} = 0;
|
||||
|
||||
# ... need to check that we don't reformat any of the received lines.
|
||||
#
|
||||
# 3.8.2 Received Lines in Gatewaying
|
||||
# When forwarding a message into or out of the Internet environment, a
|
||||
# gateway MUST prepend a Received: line, but it MUST NOT alter in any
|
||||
# way a Received: line that is already in the header.
|
||||
# ... need to check that we don't reformat any of the received lines.
|
||||
#
|
||||
# 3.8.2 Received Lines in Gatewaying
|
||||
# When forwarding a message into or out of the Internet environment, a
|
||||
# gateway MUST prepend a Received: line, but it MUST NOT alter in any
|
||||
# way a Received: line that is already in the header.
|
||||
my @header_lines = split(/^/m, $self->{header_lines});
|
||||
|
||||
my $header = Mail::Header->new(\@header_lines,
|
||||
Modify => 0, MailFrom => "COERCE");
|
||||
my $header =
|
||||
Mail::Header->new(
|
||||
\@header_lines,
|
||||
Modify => 0,
|
||||
MailFrom => "COERCE"
|
||||
);
|
||||
$self->transaction->header($header);
|
||||
$self->transaction->body_write($self->{header_lines});
|
||||
$self->{header_lines} = '';
|
||||
|
||||
#$header->add("X-SMTPD", "qpsmtpd/".$self->version.", http://smtpd.develooper.com/");
|
||||
#$header->add("X-SMTPD", "qpsmtpd/".$self->version.", http://smtpd.develooper.com/");
|
||||
|
||||
# FIXME - call plugins to work on just the header here; can
|
||||
# save us buffering the mail content.
|
||||
@ -299,7 +312,6 @@ sub got_data {
|
||||
$self->{data_size} += length $data;
|
||||
}
|
||||
|
||||
|
||||
if ($done) {
|
||||
$self->end_of_data;
|
||||
$self->end_get_chunks($remainder);
|
||||
@ -312,7 +324,8 @@ sub end_of_data {
|
||||
|
||||
#$self->log(LOGDEBUG, "size is at $size\n") unless ($i % 300);
|
||||
|
||||
$self->log(LOGDEBUG, "max_size: $self->{max_size} / size: $self->{data_size}");
|
||||
$self->log(LOGDEBUG,
|
||||
"max_size: $self->{max_size} / size: $self->{data_size}");
|
||||
|
||||
my $header = $self->transaction->header;
|
||||
if (!$header) {
|
||||
@ -321,25 +334,30 @@ sub end_of_data {
|
||||
}
|
||||
|
||||
my $smtp = $self->connection->hello eq "ehlo" ? "ESMTP" : "SMTP";
|
||||
my $esmtp = substr($smtp,0,1) eq "E";
|
||||
my $esmtp = substr($smtp, 0, 1) eq "E";
|
||||
my $authheader;
|
||||
my $sslheader;
|
||||
|
||||
if (defined $self->connection->notes('tls_enabled')
|
||||
and $self->connection->notes('tls_enabled'))
|
||||
and $self->connection->notes('tls_enabled'))
|
||||
{
|
||||
$smtp .= "S" if $esmtp; # RFC3848
|
||||
$sslheader = "(".$self->connection->notes('tls_socket')->get_cipher()." encrypted) ";
|
||||
$smtp .= "S" if $esmtp; # RFC3848
|
||||
$sslheader = "("
|
||||
. $self->connection->notes('tls_socket')->get_cipher()
|
||||
. " encrypted) ";
|
||||
}
|
||||
|
||||
if (defined $self->{_auth} and $self->{_auth} == OK) {
|
||||
$smtp .= "A" if $esmtp; # RFC3848
|
||||
$authheader = "(smtp-auth username $self->{_auth_user}, mechanism $self->{_auth_mechanism})\n";
|
||||
$smtp .= "A" if $esmtp; # RFC3848
|
||||
$authheader =
|
||||
"(smtp-auth username $self->{_auth_user}, mechanism $self->{_auth_mechanism})\n";
|
||||
}
|
||||
|
||||
$header->add("Received", $self->received_line($smtp, $authheader, $sslheader), 0);
|
||||
$header->add("Received",
|
||||
$self->received_line($smtp, $authheader, $sslheader), 0);
|
||||
|
||||
return $self->respond(552, "Message too big!") if $self->{max_size} and $self->{data_size} > $self->{max_size};
|
||||
return $self->respond(552, "Message too big!")
|
||||
if $self->{max_size} and $self->{data_size} > $self->{max_size};
|
||||
|
||||
my ($rc, $msg) = $self->run_hooks("data_post");
|
||||
return 1;
|
||||
|
@ -21,125 +21,131 @@ use vars qw(@ISA);
|
||||
my %rec_types;
|
||||
|
||||
sub init {
|
||||
my ($self) = @_;
|
||||
my ($self) = @_;
|
||||
|
||||
%rec_types = (
|
||||
REC_TYPE_SIZE => 'C', # first record, created by cleanup
|
||||
REC_TYPE_TIME => 'T', # time stamp, required
|
||||
REC_TYPE_FULL => 'F', # full name, optional
|
||||
REC_TYPE_INSP => 'I', # inspector transport
|
||||
REC_TYPE_FILT => 'L', # loop filter transport
|
||||
REC_TYPE_FROM => 'S', # sender, required
|
||||
REC_TYPE_DONE => 'D', # delivered recipient, optional
|
||||
REC_TYPE_RCPT => 'R', # todo recipient, optional
|
||||
REC_TYPE_ORCP => 'O', # original recipient, optional
|
||||
REC_TYPE_WARN => 'W', # warning message time
|
||||
REC_TYPE_ATTR => 'A', # named attribute for extensions
|
||||
%rec_types = (
|
||||
REC_TYPE_SIZE => 'C', # first record, created by cleanup
|
||||
REC_TYPE_TIME => 'T', # time stamp, required
|
||||
REC_TYPE_FULL => 'F', # full name, optional
|
||||
REC_TYPE_INSP => 'I', # inspector transport
|
||||
REC_TYPE_FILT => 'L', # loop filter transport
|
||||
REC_TYPE_FROM => 'S', # sender, required
|
||||
REC_TYPE_DONE => 'D', # delivered recipient, optional
|
||||
REC_TYPE_RCPT => 'R', # todo recipient, optional
|
||||
REC_TYPE_ORCP => 'O', # original recipient, optional
|
||||
REC_TYPE_WARN => 'W', # warning message time
|
||||
REC_TYPE_ATTR => 'A', # named attribute for extensions
|
||||
|
||||
REC_TYPE_MESG => 'M', # start message records
|
||||
REC_TYPE_MESG => 'M', # start message records
|
||||
|
||||
REC_TYPE_CONT => 'L', # long data record
|
||||
REC_TYPE_NORM => 'N', # normal data record
|
||||
REC_TYPE_CONT => 'L', # long data record
|
||||
REC_TYPE_NORM => 'N', # normal data record
|
||||
|
||||
REC_TYPE_XTRA => 'X', # start extracted records
|
||||
REC_TYPE_XTRA => 'X', # start extracted records
|
||||
|
||||
REC_TYPE_RRTO => 'r', # return-receipt, from headers
|
||||
REC_TYPE_ERTO => 'e', # errors-to, from headers
|
||||
REC_TYPE_PRIO => 'P', # priority
|
||||
REC_TYPE_VERP => 'V', # VERP delimiters
|
||||
REC_TYPE_RRTO => 'r', # return-receipt, from headers
|
||||
REC_TYPE_ERTO => 'e', # errors-to, from headers
|
||||
REC_TYPE_PRIO => 'P', # priority
|
||||
REC_TYPE_VERP => 'V', # VERP delimiters
|
||||
|
||||
REC_TYPE_END => 'E', # terminator, required
|
||||
REC_TYPE_END => 'E', # terminator, required
|
||||
|
||||
);
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
sub print_rec {
|
||||
my ($self, $type, @list) = @_;
|
||||
my ($self, $type, @list) = @_;
|
||||
|
||||
die "unknown record type" unless ($rec_types{$type});
|
||||
$self->print($rec_types{$type});
|
||||
die "unknown record type" unless ($rec_types{$type});
|
||||
$self->print($rec_types{$type});
|
||||
|
||||
# the length is a little endian base-128 number where each
|
||||
# byte except the last has the high bit set:
|
||||
my $s = "@list";
|
||||
my $ln = length($s);
|
||||
while ($ln >= 0x80) {
|
||||
my $lnl = $ln & 0x7F;
|
||||
$ln >>= 7;
|
||||
$self->print(chr($lnl | 0x80));
|
||||
}
|
||||
$self->print(chr($ln));
|
||||
# the length is a little endian base-128 number where each
|
||||
# byte except the last has the high bit set:
|
||||
my $s = "@list";
|
||||
my $ln = length($s);
|
||||
while ($ln >= 0x80) {
|
||||
my $lnl = $ln & 0x7F;
|
||||
$ln >>= 7;
|
||||
$self->print(chr($lnl | 0x80));
|
||||
}
|
||||
$self->print(chr($ln));
|
||||
|
||||
$self->print($s);
|
||||
$self->print($s);
|
||||
}
|
||||
|
||||
sub print_rec_size {
|
||||
my ($self, $content_size, $data_offset, $rcpt_count) = @_;
|
||||
my ($self, $content_size, $data_offset, $rcpt_count) = @_;
|
||||
|
||||
my $s = sprintf("%15ld %15ld %15ld", $content_size, $data_offset, $rcpt_count);
|
||||
$self->print_rec('REC_TYPE_SIZE', $s);
|
||||
my $s =
|
||||
sprintf("%15ld %15ld %15ld", $content_size, $data_offset, $rcpt_count);
|
||||
$self->print_rec('REC_TYPE_SIZE', $s);
|
||||
}
|
||||
|
||||
sub print_rec_time {
|
||||
my ($self, $time) = @_;
|
||||
my ($self, $time) = @_;
|
||||
|
||||
$time = time() unless (defined($time));
|
||||
$time = time() unless (defined($time));
|
||||
|
||||
my $s = sprintf("%d", $time);
|
||||
$self->print_rec('REC_TYPE_TIME', $s);
|
||||
my $s = sprintf("%d", $time);
|
||||
$self->print_rec('REC_TYPE_TIME', $s);
|
||||
}
|
||||
|
||||
sub open_cleanup {
|
||||
my ($class, $socket) = @_;
|
||||
my ($class, $socket) = @_;
|
||||
|
||||
my $self;
|
||||
if ($socket =~ m#^(/.+)#) {
|
||||
$socket = $1; # un-taint socket path
|
||||
$self = IO::Socket::UNIX->new(Type => SOCK_STREAM,
|
||||
Peer => $socket) if $socket;
|
||||
my $self;
|
||||
if ($socket =~ m#^(/.+)#) {
|
||||
$socket = $1; # un-taint socket path
|
||||
$self = IO::Socket::UNIX->new(Type => SOCK_STREAM,
|
||||
Peer => $socket)
|
||||
if $socket;
|
||||
|
||||
} elsif ($socket =~ /(.*):(\d+)/) {
|
||||
my ($host,$port) = ($1,$2); # un-taint address and port
|
||||
$self = IO::Socket::INET->new(Proto => 'tcp',
|
||||
PeerAddr => $host,PeerPort => $port)
|
||||
if $host and $port;
|
||||
}
|
||||
unless (ref $self) {
|
||||
warn "Couldn't open \"$socket\": $!";
|
||||
return;
|
||||
}
|
||||
# allow buffered writes
|
||||
$self->autoflush(0);
|
||||
bless ($self, $class);
|
||||
$self->init();
|
||||
return $self;
|
||||
}
|
||||
elsif ($socket =~ /(.*):(\d+)/) {
|
||||
my ($host, $port) = ($1, $2); # un-taint address and port
|
||||
$self = IO::Socket::INET->new(
|
||||
Proto => 'tcp',
|
||||
PeerAddr => $host,
|
||||
PeerPort => $port
|
||||
)
|
||||
if $host and $port;
|
||||
}
|
||||
unless (ref $self) {
|
||||
warn "Couldn't open \"$socket\": $!";
|
||||
return;
|
||||
}
|
||||
|
||||
# allow buffered writes
|
||||
$self->autoflush(0);
|
||||
bless($self, $class);
|
||||
$self->init();
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub print_attr {
|
||||
my ($self, @kv) = @_;
|
||||
for (@kv) {
|
||||
$self->print("$_\0");
|
||||
}
|
||||
$self->print("\0");
|
||||
my ($self, @kv) = @_;
|
||||
for (@kv) {
|
||||
$self->print("$_\0");
|
||||
}
|
||||
$self->print("\0");
|
||||
}
|
||||
|
||||
sub get_attr {
|
||||
my ($self) = @_;
|
||||
local $/ = "\0";
|
||||
my %kv;
|
||||
for(;;) {
|
||||
my $k = $self->getline;
|
||||
chomp($k);
|
||||
last unless ($k);
|
||||
my $v = $self->getline;
|
||||
chomp($v);
|
||||
$kv{$k} = $v;
|
||||
}
|
||||
return %kv;
|
||||
my ($self) = @_;
|
||||
local $/ = "\0";
|
||||
my %kv;
|
||||
for (; ;) {
|
||||
my $k = $self->getline;
|
||||
chomp($k);
|
||||
last unless ($k);
|
||||
my $v = $self->getline;
|
||||
chomp($v);
|
||||
$kv{$k} = $v;
|
||||
}
|
||||
return %kv;
|
||||
}
|
||||
|
||||
|
||||
=head2 print_msg_line($line)
|
||||
|
||||
print one line of a message to cleanup.
|
||||
@ -151,17 +157,17 @@ and splits the line across several records if it is longer than
|
||||
=cut
|
||||
|
||||
sub print_msg_line {
|
||||
my ($self, $line) = @_;
|
||||
my ($self, $line) = @_;
|
||||
|
||||
$line =~ s/\r?\n$//s;
|
||||
$line =~ s/\r?\n$//s;
|
||||
|
||||
# split into 1k chunks.
|
||||
while (length($line) > 1024) {
|
||||
my $s = substr($line, 0, 1024);
|
||||
$line = substr($line, 1024);
|
||||
$self->print_rec('REC_TYPE_CONT', $s);
|
||||
}
|
||||
$self->print_rec('REC_TYPE_NORM', $line);
|
||||
# split into 1k chunks.
|
||||
while (length($line) > 1024) {
|
||||
my $s = substr($line, 0, 1024);
|
||||
$line = substr($line, 1024);
|
||||
$self->print_rec('REC_TYPE_CONT', $s);
|
||||
}
|
||||
$self->print_rec('REC_TYPE_NORM', $line);
|
||||
}
|
||||
|
||||
=head2 inject_mail($transaction)
|
||||
@ -172,52 +178,55 @@ $transaction is supposed to be a Qpsmtpd::Transaction object.
|
||||
=cut
|
||||
|
||||
sub inject_mail {
|
||||
my ($class, $transaction) = @_;
|
||||
my ($class, $transaction) = @_;
|
||||
|
||||
my @sockets = @{$transaction->notes('postfix-queue-sockets')
|
||||
// ['/var/spool/postfix/public/cleanup']};
|
||||
my $strm;
|
||||
$strm = $class->open_cleanup($_) and last for @sockets;
|
||||
die "Unable to open any cleanup sockets!" unless $strm;
|
||||
my @sockets = @{$transaction->notes('postfix-queue-sockets')
|
||||
// ['/var/spool/postfix/public/cleanup']};
|
||||
my $strm;
|
||||
$strm = $class->open_cleanup($_) and last for @sockets;
|
||||
die "Unable to open any cleanup sockets!" unless $strm;
|
||||
|
||||
my %at = $strm->get_attr;
|
||||
my $qid = $at{queue_id};
|
||||
print STDERR "qid=$qid\n";
|
||||
$strm->print_attr('flags' => $transaction->notes('postfix-queue-flags'));
|
||||
$strm->print_rec_time();
|
||||
$strm->print_rec('REC_TYPE_FROM', $transaction->sender->address|| "");
|
||||
for (map { $_->address } $transaction->recipients) {
|
||||
$strm->print_rec('REC_TYPE_RCPT', $_);
|
||||
}
|
||||
# add an empty message length record.
|
||||
# cleanup is supposed to understand that.
|
||||
# see src/pickup/pickup.c
|
||||
$strm->print_rec('REC_TYPE_MESG', "");
|
||||
my %at = $strm->get_attr;
|
||||
my $qid = $at{queue_id};
|
||||
print STDERR "qid=$qid\n";
|
||||
$strm->print_attr('flags' => $transaction->notes('postfix-queue-flags'));
|
||||
$strm->print_rec_time();
|
||||
$strm->print_rec('REC_TYPE_FROM', $transaction->sender->address || "");
|
||||
for (map { $_->address } $transaction->recipients) {
|
||||
$strm->print_rec('REC_TYPE_RCPT', $_);
|
||||
}
|
||||
|
||||
# a received header has already been added in SMTP.pm
|
||||
# so we can just copy the message:
|
||||
# add an empty message length record.
|
||||
# cleanup is supposed to understand that.
|
||||
# see src/pickup/pickup.c
|
||||
$strm->print_rec('REC_TYPE_MESG', "");
|
||||
|
||||
my $hdr = $transaction->header->as_string;
|
||||
for (split(/\r?\n/, $hdr)) {
|
||||
print STDERR "hdr: $_\n";
|
||||
$strm->print_msg_line($_);
|
||||
}
|
||||
$transaction->body_resetpos;
|
||||
while (my $line = $transaction->body_getline) {
|
||||
# print STDERR "body: $line\n";
|
||||
$strm->print_msg_line($line);
|
||||
}
|
||||
# a received header has already been added in SMTP.pm
|
||||
# so we can just copy the message:
|
||||
|
||||
# finish it.
|
||||
$strm->print_rec('REC_TYPE_XTRA', "");
|
||||
$strm->print_rec('REC_TYPE_END', "");
|
||||
$strm->flush();
|
||||
%at = $strm->get_attr;
|
||||
my $status = $at{status};
|
||||
my $reason = $at{reason};
|
||||
$strm->close();
|
||||
return wantarray ? ($status, $qid, $reason || "") : $status;
|
||||
my $hdr = $transaction->header->as_string;
|
||||
for (split(/\r?\n/, $hdr)) {
|
||||
print STDERR "hdr: $_\n";
|
||||
$strm->print_msg_line($_);
|
||||
}
|
||||
$transaction->body_resetpos;
|
||||
while (my $line = $transaction->body_getline) {
|
||||
|
||||
# print STDERR "body: $line\n";
|
||||
$strm->print_msg_line($line);
|
||||
}
|
||||
|
||||
# finish it.
|
||||
$strm->print_rec('REC_TYPE_XTRA', "");
|
||||
$strm->print_rec('REC_TYPE_END', "");
|
||||
$strm->flush();
|
||||
%at = $strm->get_attr;
|
||||
my $status = $at{status};
|
||||
my $reason = $at{reason};
|
||||
$strm->close();
|
||||
return wantarray ? ($status, $qid, $reason || "") : $status;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
# vim:sw=2
|
||||
|
@ -15,72 +15,79 @@ require Exporter;
|
||||
use vars qw(@ISA @EXPORT %cleanup_soft %cleanup_hard $postfix_version);
|
||||
use strict;
|
||||
|
||||
@ISA = qw(Exporter);
|
||||
@ISA = qw(Exporter);
|
||||
@EXPORT = qw(
|
||||
%cleanup_soft
|
||||
%cleanup_hard
|
||||
$postfix_version
|
||||
CLEANUP_FLAG_NONE
|
||||
CLEANUP_FLAG_BOUNCE
|
||||
CLEANUP_FLAG_FILTER
|
||||
CLEANUP_FLAG_HOLD
|
||||
CLEANUP_FLAG_DISCARD
|
||||
CLEANUP_FLAG_BCC_OK
|
||||
CLEANUP_FLAG_MAP_OK
|
||||
CLEANUP_FLAG_MILTER
|
||||
CLEANUP_FLAG_FILTER_ALL
|
||||
CLEANUP_FLAG_MASK_EXTERNAL
|
||||
CLEANUP_FLAG_MASK_INTERNAL
|
||||
CLEANUP_FLAG_MASK_EXTRA
|
||||
CLEANUP_STAT_OK
|
||||
CLEANUP_STAT_BAD
|
||||
CLEANUP_STAT_WRITE
|
||||
CLEANUP_STAT_SIZE
|
||||
CLEANUP_STAT_CONT
|
||||
CLEANUP_STAT_HOPS
|
||||
CLEANUP_STAT_RCPT
|
||||
CLEANUP_STAT_PROXY
|
||||
CLEANUP_STAT_DEFER
|
||||
CLEANUP_STAT_MASK_CANT_BOUNCE
|
||||
CLEANUP_STAT_MASK_INCOMPLETE
|
||||
);
|
||||
%cleanup_soft
|
||||
%cleanup_hard
|
||||
$postfix_version
|
||||
CLEANUP_FLAG_NONE
|
||||
CLEANUP_FLAG_BOUNCE
|
||||
CLEANUP_FLAG_FILTER
|
||||
CLEANUP_FLAG_HOLD
|
||||
CLEANUP_FLAG_DISCARD
|
||||
CLEANUP_FLAG_BCC_OK
|
||||
CLEANUP_FLAG_MAP_OK
|
||||
CLEANUP_FLAG_MILTER
|
||||
CLEANUP_FLAG_FILTER_ALL
|
||||
CLEANUP_FLAG_MASK_EXTERNAL
|
||||
CLEANUP_FLAG_MASK_INTERNAL
|
||||
CLEANUP_FLAG_MASK_EXTRA
|
||||
CLEANUP_STAT_OK
|
||||
CLEANUP_STAT_BAD
|
||||
CLEANUP_STAT_WRITE
|
||||
CLEANUP_STAT_SIZE
|
||||
CLEANUP_STAT_CONT
|
||||
CLEANUP_STAT_HOPS
|
||||
CLEANUP_STAT_RCPT
|
||||
CLEANUP_STAT_PROXY
|
||||
CLEANUP_STAT_DEFER
|
||||
CLEANUP_STAT_MASK_CANT_BOUNCE
|
||||
CLEANUP_STAT_MASK_INCOMPLETE
|
||||
);
|
||||
|
||||
$postfix_version = "2.4";
|
||||
use constant CLEANUP_FLAG_NONE => 0; # /* No special features */
|
||||
use constant CLEANUP_FLAG_BOUNCE => (1<<0); # /* Bounce bad messages */
|
||||
use constant CLEANUP_FLAG_FILTER => (1<<1); # /* Enable header/body checks */
|
||||
use constant CLEANUP_FLAG_HOLD => (1<<2); # /* Place message on hold */
|
||||
use constant CLEANUP_FLAG_DISCARD => (1<<3); # /* Discard message silently */
|
||||
use constant CLEANUP_FLAG_BCC_OK => (1<<4); # /* Ok to add auto-BCC addresses */
|
||||
use constant CLEANUP_FLAG_MAP_OK => (1<<5); # /* Ok to map addresses */
|
||||
use constant CLEANUP_FLAG_MILTER => (1<<6); # /* Enable Milter applications */
|
||||
use constant CLEANUP_FLAG_FILTER_ALL => (CLEANUP_FLAG_FILTER | CLEANUP_FLAG_MILTER);
|
||||
use constant CLEANUP_FLAG_MASK_EXTERNAL => (CLEANUP_FLAG_FILTER_ALL | CLEANUP_FLAG_BCC_OK | CLEANUP_FLAG_MAP_OK);
|
||||
use constant CLEANUP_FLAG_NONE => 0; # /* No special features */
|
||||
use constant CLEANUP_FLAG_BOUNCE => (1 << 0); # /* Bounce bad messages */
|
||||
use constant CLEANUP_FLAG_FILTER => (1 << 1); # /* Enable header/body checks */
|
||||
use constant CLEANUP_FLAG_HOLD => (1 << 2); # /* Place message on hold */
|
||||
use constant CLEANUP_FLAG_DISCARD => (1 << 3); # /* Discard message silently */
|
||||
use constant CLEANUP_FLAG_BCC_OK => (1 << 4)
|
||||
; # /* Ok to add auto-BCC addresses */
|
||||
use constant CLEANUP_FLAG_MAP_OK => (1 << 5); # /* Ok to map addresses */
|
||||
use constant CLEANUP_FLAG_MILTER => (1 << 6); # /* Enable Milter applications */
|
||||
use constant CLEANUP_FLAG_FILTER_ALL =>
|
||||
(CLEANUP_FLAG_FILTER | CLEANUP_FLAG_MILTER);
|
||||
use constant CLEANUP_FLAG_MASK_EXTERNAL =>
|
||||
(CLEANUP_FLAG_FILTER_ALL | CLEANUP_FLAG_BCC_OK | CLEANUP_FLAG_MAP_OK);
|
||||
use constant CLEANUP_FLAG_MASK_INTERNAL => CLEANUP_FLAG_MAP_OK;
|
||||
use constant CLEANUP_FLAG_MASK_EXTRA => (CLEANUP_FLAG_HOLD | CLEANUP_FLAG_DISCARD);
|
||||
use constant CLEANUP_FLAG_MASK_EXTRA =>
|
||||
(CLEANUP_FLAG_HOLD | CLEANUP_FLAG_DISCARD);
|
||||
|
||||
use constant CLEANUP_STAT_OK => 0; # /* Success. */
|
||||
use constant CLEANUP_STAT_BAD => (1<<0); # /* Internal protocol error */
|
||||
use constant CLEANUP_STAT_WRITE => (1<<1); # /* Error writing message file */
|
||||
use constant CLEANUP_STAT_SIZE => (1<<2); # /* Message file too big */
|
||||
use constant CLEANUP_STAT_CONT => (1<<3); # /* Message content rejected */
|
||||
use constant CLEANUP_STAT_HOPS => (1<<4); # /* Too many hops */
|
||||
use constant CLEANUP_STAT_RCPT => (1<<6); # /* No recipients found */
|
||||
use constant CLEANUP_STAT_PROXY => (1<<7); # /* Proxy reject */
|
||||
use constant CLEANUP_STAT_DEFER => (1<<8); # /* Temporary reject */
|
||||
use constant CLEANUP_STAT_MASK_CANT_BOUNCE => (CLEANUP_STAT_BAD | CLEANUP_STAT_WRITE | CLEANUP_STAT_DEFER);
|
||||
use constant CLEANUP_STAT_MASK_INCOMPLETE => (CLEANUP_STAT_BAD | CLEANUP_STAT_WRITE | CLEANUP_STAT_SIZE | CLEANUP_STAT_DEFER);
|
||||
use constant CLEANUP_STAT_OK => 0; # /* Success. */
|
||||
use constant CLEANUP_STAT_BAD => (1 << 0); # /* Internal protocol error */
|
||||
use constant CLEANUP_STAT_WRITE => (1 << 1); # /* Error writing message file */
|
||||
use constant CLEANUP_STAT_SIZE => (1 << 2); # /* Message file too big */
|
||||
use constant CLEANUP_STAT_CONT => (1 << 3); # /* Message content rejected */
|
||||
use constant CLEANUP_STAT_HOPS => (1 << 4); # /* Too many hops */
|
||||
use constant CLEANUP_STAT_RCPT => (1 << 6); # /* No recipients found */
|
||||
use constant CLEANUP_STAT_PROXY => (1 << 7); # /* Proxy reject */
|
||||
use constant CLEANUP_STAT_DEFER => (1 << 8); # /* Temporary reject */
|
||||
use constant CLEANUP_STAT_MASK_CANT_BOUNCE =>
|
||||
(CLEANUP_STAT_BAD | CLEANUP_STAT_WRITE | CLEANUP_STAT_DEFER);
|
||||
use constant CLEANUP_STAT_MASK_INCOMPLETE =>
|
||||
(CLEANUP_STAT_BAD | CLEANUP_STAT_WRITE | CLEANUP_STAT_SIZE |
|
||||
CLEANUP_STAT_DEFER);
|
||||
|
||||
%cleanup_soft = (
|
||||
CLEANUP_STAT_DEFER => "service unavailable (#4.7.1)",
|
||||
CLEANUP_STAT_PROXY => "queue file write error (#4.3.0)",
|
||||
CLEANUP_STAT_BAD => "internal protocol error (#4.3.0)",
|
||||
CLEANUP_STAT_WRITE => "queue file write error (#4.3.0)",
|
||||
);
|
||||
CLEANUP_STAT_DEFER => "service unavailable (#4.7.1)",
|
||||
CLEANUP_STAT_PROXY => "queue file write error (#4.3.0)",
|
||||
CLEANUP_STAT_BAD => "internal protocol error (#4.3.0)",
|
||||
CLEANUP_STAT_WRITE => "queue file write error (#4.3.0)",
|
||||
);
|
||||
%cleanup_hard = (
|
||||
CLEANUP_STAT_RCPT => "no recipients specified (#5.1.0)",
|
||||
CLEANUP_STAT_HOPS => "too many hops (#5.4.0)",
|
||||
CLEANUP_STAT_SIZE => "message file too big (#5.3.4)",
|
||||
CLEANUP_STAT_CONT => "message content rejected (#5.7.1)",
|
||||
);
|
||||
CLEANUP_STAT_RCPT => "no recipients specified (#5.1.0)",
|
||||
CLEANUP_STAT_HOPS => "too many hops (#5.4.0)",
|
||||
CLEANUP_STAT_SIZE => "message file too big (#5.3.4)",
|
||||
CLEANUP_STAT_CONT => "message content rejected (#5.7.1)",
|
||||
);
|
||||
1;
|
||||
|
1169
lib/Qpsmtpd/SMTP.pm
1169
lib/Qpsmtpd/SMTP.pm
File diff suppressed because it is too large
Load Diff
@ -4,27 +4,28 @@ use Qpsmtpd::Constants;
|
||||
@ISA = qw(Qpsmtpd::SMTP);
|
||||
|
||||
sub dispatch {
|
||||
my $self = shift;
|
||||
my ($cmd) = lc shift;
|
||||
my $self = shift;
|
||||
my ($cmd) = lc shift;
|
||||
|
||||
$self->{_counter}++;
|
||||
$self->{_counter}++;
|
||||
|
||||
if ($cmd !~ /^(\w{1,12})$/ or !exists $self->{_commands}->{$1}) {
|
||||
$self->run_hooks("unrecognized_command", $cmd, @_);
|
||||
return 1;
|
||||
}
|
||||
$cmd = $1;
|
||||
|
||||
if (1 or $self->{_commands}->{$cmd} and $self->can($cmd)) {
|
||||
my ($result) = eval { $self->$cmd(@_) };
|
||||
if ($@ =~ /^disconnect_tcpserver/) {
|
||||
die "disconnect_tcpserver";
|
||||
} elsif ($@) {
|
||||
$self->log(LOGERROR, "XX: $@") if $@;
|
||||
if ($cmd !~ /^(\w{1,12})$/ or !exists $self->{_commands}->{$1}) {
|
||||
$self->run_hooks("unrecognized_command", $cmd, @_);
|
||||
return 1;
|
||||
}
|
||||
return $result if defined $result;
|
||||
return $self->fault("command '$cmd' failed unexpectedly");
|
||||
}
|
||||
$cmd = $1;
|
||||
|
||||
return;
|
||||
if (1 or $self->{_commands}->{$cmd} and $self->can($cmd)) {
|
||||
my ($result) = eval { $self->$cmd(@_) };
|
||||
if ($@ =~ /^disconnect_tcpserver/) {
|
||||
die "disconnect_tcpserver";
|
||||
}
|
||||
elsif ($@) {
|
||||
$self->log(LOGERROR, "XX: $@") if $@;
|
||||
}
|
||||
return $result if defined $result;
|
||||
return $self->fault("command '$cmd' failed unexpectedly");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -10,12 +10,15 @@ use POSIX ();
|
||||
|
||||
my $has_ipv6 = 0;
|
||||
if (
|
||||
eval {require Socket6;} &&
|
||||
eval { require Socket6; }
|
||||
&&
|
||||
|
||||
# INET6 prior to 2.01 will not work; sorry.
|
||||
eval {require IO::Socket::INET6; IO::Socket::INET6->VERSION("2.00");}
|
||||
) {
|
||||
eval { require IO::Socket::INET6; IO::Socket::INET6->VERSION("2.00"); }
|
||||
)
|
||||
{
|
||||
Socket6->import(qw(inet_ntop));
|
||||
$has_ipv6=1;
|
||||
$has_ipv6 = 1;
|
||||
}
|
||||
|
||||
sub has_ipv6 {
|
||||
@ -33,25 +36,31 @@ sub start_connection {
|
||||
);
|
||||
|
||||
if ($ENV{TCPREMOTEIP}) {
|
||||
# started from tcpserver (or some other superserver which
|
||||
# exports the TCPREMOTE* variables.
|
||||
$remote_ip = $ENV{TCPREMOTEIP};
|
||||
$remote_host = $ENV{TCPREMOTEHOST} || "[$remote_ip]";
|
||||
$remote_info = $ENV{TCPREMOTEINFO} ? "$ENV{TCPREMOTEINFO}\@$remote_host" : $remote_host;
|
||||
|
||||
# started from tcpserver (or some other superserver which
|
||||
# exports the TCPREMOTE* variables.
|
||||
$remote_ip = $ENV{TCPREMOTEIP};
|
||||
$remote_host = $ENV{TCPREMOTEHOST} || "[$remote_ip]";
|
||||
$remote_info =
|
||||
$ENV{TCPREMOTEINFO}
|
||||
? "$ENV{TCPREMOTEINFO}\@$remote_host"
|
||||
: $remote_host;
|
||||
$remote_port = $ENV{TCPREMOTEPORT};
|
||||
$local_ip = $ENV{TCPLOCALIP};
|
||||
$local_port = $ENV{TCPLOCALPORT};
|
||||
$local_host = $ENV{TCPLOCALHOST};
|
||||
} else {
|
||||
# Started from inetd or similar.
|
||||
# get info on the remote host from the socket.
|
||||
# ignore ident/tap/...
|
||||
my $hersockaddr = getpeername(STDIN)
|
||||
or die "getpeername failed: $0 must be called from tcpserver, (x)inetd or a similar program which passes a socket to stdin";
|
||||
my ($port, $iaddr) = sockaddr_in($hersockaddr);
|
||||
$remote_ip = inet_ntoa($iaddr);
|
||||
$remote_host = gethostbyaddr($iaddr, AF_INET) || "[$remote_ip]";
|
||||
$remote_info = $remote_host;
|
||||
}
|
||||
else {
|
||||
# Started from inetd or similar.
|
||||
# get info on the remote host from the socket.
|
||||
# ignore ident/tap/...
|
||||
my $hersockaddr = getpeername(STDIN)
|
||||
or die
|
||||
"getpeername failed: $0 must be called from tcpserver, (x)inetd or a similar program which passes a socket to stdin";
|
||||
my ($port, $iaddr) = sockaddr_in($hersockaddr);
|
||||
$remote_ip = inet_ntoa($iaddr);
|
||||
$remote_host = gethostbyaddr($iaddr, AF_INET) || "[$remote_ip]";
|
||||
$remote_info = $remote_host;
|
||||
}
|
||||
$self->log(LOGNOTICE, "Connection from $remote_info [$remote_ip]");
|
||||
|
||||
@ -64,20 +73,22 @@ sub start_connection {
|
||||
my $now = POSIX::strftime("%H:%M:%S %Y-%m-%d", localtime);
|
||||
$0 = "$first_0 [$remote_ip : $remote_host : $now]";
|
||||
|
||||
$self->SUPER::connection->start(remote_info => $remote_info,
|
||||
$self->SUPER::connection->start(
|
||||
remote_info => $remote_info,
|
||||
remote_ip => $remote_ip,
|
||||
remote_host => $remote_host,
|
||||
remote_port => $remote_port,
|
||||
local_ip => $local_ip,
|
||||
local_port => $local_port,
|
||||
local_host => $local_host,
|
||||
@_);
|
||||
@_
|
||||
);
|
||||
}
|
||||
|
||||
sub run {
|
||||
my ($self, $client) = @_;
|
||||
|
||||
# Set local client_socket to passed client object for testing socket state on writes
|
||||
# Set local client_socket to passed client object for testing socket state on writes
|
||||
$self->{__client_socket} = $client;
|
||||
|
||||
$self->load_plugins unless $self->{hooks};
|
||||
@ -85,107 +96,121 @@ sub run {
|
||||
my $rc = $self->start_conversation;
|
||||
return if $rc != DONE;
|
||||
|
||||
# this should really be the loop and read_input should just get one line; I think
|
||||
# this should really be the loop and read_input should just get one line; I think
|
||||
$self->read_input;
|
||||
}
|
||||
|
||||
sub read_input {
|
||||
my $self = shift;
|
||||
my $self = shift;
|
||||
|
||||
my $timeout =
|
||||
$self->config('timeoutsmtpd') # qmail smtpd control file
|
||||
|| $self->config('timeout') # qpsmtpd control file
|
||||
|| 1200; # default value
|
||||
my $timeout = $self->config('timeoutsmtpd') # qmail smtpd control file
|
||||
|| $self->config('timeout') # qpsmtpd control file
|
||||
|| 1200; # default value
|
||||
|
||||
alarm $timeout;
|
||||
while (<STDIN>) {
|
||||
alarm 0;
|
||||
$_ =~ s/\r?\n$//s; # advanced chomp
|
||||
$self->log(LOGINFO, "dispatching $_");
|
||||
$self->connection->notes('original_string', $_);
|
||||
defined $self->dispatch(split / +/, $_, 2)
|
||||
or $self->respond(502, "command unrecognized: '$_'");
|
||||
alarm $timeout;
|
||||
}
|
||||
alarm(0);
|
||||
return if $self->connection->notes('disconnected');
|
||||
$self->reset_transaction;
|
||||
$self->run_hooks('disconnect');
|
||||
$self->connection->notes(disconnected => 1);
|
||||
while (<STDIN>) {
|
||||
alarm 0;
|
||||
$_ =~ s/\r?\n$//s; # advanced chomp
|
||||
$self->log(LOGINFO, "dispatching $_");
|
||||
$self->connection->notes('original_string', $_);
|
||||
defined $self->dispatch(split / +/, $_, 2)
|
||||
or $self->respond(502, "command unrecognized: '$_'");
|
||||
alarm $timeout;
|
||||
}
|
||||
alarm(0);
|
||||
return if $self->connection->notes('disconnected');
|
||||
$self->reset_transaction;
|
||||
$self->run_hooks('disconnect');
|
||||
$self->connection->notes(disconnected => 1);
|
||||
}
|
||||
|
||||
sub respond {
|
||||
my ($self, $code, @messages) = @_;
|
||||
my $buf = '';
|
||||
my ($self, $code, @messages) = @_;
|
||||
my $buf = '';
|
||||
|
||||
if ( !$self->check_socket() ) {
|
||||
$self->log(LOGERROR, "Lost connection to client, cannot send response.");
|
||||
return(0);
|
||||
}
|
||||
if (!$self->check_socket()) {
|
||||
$self->log(LOGERROR,
|
||||
"Lost connection to client, cannot send response.");
|
||||
return (0);
|
||||
}
|
||||
|
||||
while (my $msg = shift @messages) {
|
||||
my $line = $code . (@messages?"-":" ").$msg;
|
||||
$self->log(LOGINFO, $line);
|
||||
$buf .= "$line\r\n";
|
||||
}
|
||||
print $buf or ($self->log(LOGERROR, "Could not print [$buf]: $!"), return 0);
|
||||
return 1;
|
||||
while (my $msg = shift @messages) {
|
||||
my $line = $code . (@messages ? "-" : " ") . $msg;
|
||||
$self->log(LOGINFO, $line);
|
||||
$buf .= "$line\r\n";
|
||||
}
|
||||
print $buf
|
||||
or ($self->log(LOGERROR, "Could not print [$buf]: $!"), return 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub disconnect {
|
||||
my $self = shift;
|
||||
$self->log(LOGINFO,"click, disconnecting");
|
||||
$self->SUPER::disconnect(@_);
|
||||
$self->run_hooks("post-connection");
|
||||
$self->connection->reset;
|
||||
exit;
|
||||
my $self = shift;
|
||||
$self->log(LOGINFO, "click, disconnecting");
|
||||
$self->SUPER::disconnect(@_);
|
||||
$self->run_hooks("post-connection");
|
||||
$self->connection->reset;
|
||||
exit;
|
||||
}
|
||||
|
||||
# local/remote port and ip address
|
||||
sub lrpip {
|
||||
my ($server, $client, $hisaddr) = @_;
|
||||
my ($server, $client, $hisaddr) = @_;
|
||||
|
||||
my ($port, $iaddr) = ($server->sockdomain == AF_INET) ? (sockaddr_in($hisaddr)) : (sockaddr_in6($hisaddr));
|
||||
my $localsockaddr = getsockname($client);
|
||||
my ($lport, $laddr) = ($server->sockdomain == AF_INET) ? (sockaddr_in($localsockaddr)) : (sockaddr_in6($localsockaddr));
|
||||
my ($port, $iaddr) =
|
||||
($server->sockdomain == AF_INET)
|
||||
? (sockaddr_in($hisaddr))
|
||||
: (sockaddr_in6($hisaddr));
|
||||
my $localsockaddr = getsockname($client);
|
||||
my ($lport, $laddr) =
|
||||
($server->sockdomain == AF_INET)
|
||||
? (sockaddr_in($localsockaddr))
|
||||
: (sockaddr_in6($localsockaddr));
|
||||
|
||||
my $nto_iaddr = ($server->sockdomain == AF_INET) ? (inet_ntoa($iaddr)) : (inet_ntop(AF_INET6(), $iaddr));
|
||||
my $nto_laddr = ($server->sockdomain == AF_INET) ? (inet_ntoa($laddr)) : (inet_ntop(AF_INET6(), $laddr));
|
||||
$nto_iaddr =~ s/::ffff://;
|
||||
$nto_laddr =~ s/::ffff://;
|
||||
my $nto_iaddr =
|
||||
($server->sockdomain == AF_INET)
|
||||
? (inet_ntoa($iaddr))
|
||||
: (inet_ntop(AF_INET6(), $iaddr));
|
||||
my $nto_laddr =
|
||||
($server->sockdomain == AF_INET)
|
||||
? (inet_ntoa($laddr))
|
||||
: (inet_ntop(AF_INET6(), $laddr));
|
||||
$nto_iaddr =~ s/::ffff://;
|
||||
$nto_laddr =~ s/::ffff://;
|
||||
|
||||
return ($port, $iaddr, $lport, $laddr, $nto_iaddr, $nto_laddr);
|
||||
return ($port, $iaddr, $lport, $laddr, $nto_iaddr, $nto_laddr);
|
||||
}
|
||||
|
||||
sub tcpenv {
|
||||
my ($nto_laddr, $nto_iaddr, $no_rdns) = @_;
|
||||
my ($nto_laddr, $nto_iaddr, $no_rdns) = @_;
|
||||
|
||||
my $TCPLOCALIP = $nto_laddr;
|
||||
my $TCPREMOTEIP = $nto_iaddr;
|
||||
my $TCPLOCALIP = $nto_laddr;
|
||||
my $TCPREMOTEIP = $nto_iaddr;
|
||||
|
||||
if ($no_rdns) {
|
||||
return ($TCPLOCALIP, $TCPREMOTEIP, $TCPREMOTEIP ? "[$ENV{TCPREMOTEIP}]" : "[noip!]");
|
||||
}
|
||||
my $res = new Net::DNS::Resolver;
|
||||
$res->tcp_timeout(3);
|
||||
$res->udp_timeout(3);
|
||||
my $query = $res->query($nto_iaddr);
|
||||
my $TCPREMOTEHOST;
|
||||
if($query) {
|
||||
foreach my $rr ($query->answer) {
|
||||
next unless $rr->type eq "PTR";
|
||||
$TCPREMOTEHOST = $rr->ptrdname;
|
||||
if ($no_rdns) {
|
||||
return ($TCPLOCALIP, $TCPREMOTEIP,
|
||||
$TCPREMOTEIP ? "[$ENV{TCPREMOTEIP}]" : "[noip!]");
|
||||
}
|
||||
}
|
||||
return ($TCPLOCALIP, $TCPREMOTEIP, $TCPREMOTEHOST || "Unknown");
|
||||
my $res = new Net::DNS::Resolver;
|
||||
$res->tcp_timeout(3);
|
||||
$res->udp_timeout(3);
|
||||
my $query = $res->query($nto_iaddr);
|
||||
my $TCPREMOTEHOST;
|
||||
if ($query) {
|
||||
foreach my $rr ($query->answer) {
|
||||
next unless $rr->type eq "PTR";
|
||||
$TCPREMOTEHOST = $rr->ptrdname;
|
||||
}
|
||||
}
|
||||
return ($TCPLOCALIP, $TCPREMOTEIP, $TCPREMOTEHOST || "Unknown");
|
||||
}
|
||||
|
||||
sub check_socket() {
|
||||
my $self = shift;
|
||||
my $self = shift;
|
||||
|
||||
return 1 if ( $self->{__client_socket}->connected );
|
||||
return 1 if ($self->{__client_socket}->connected);
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
||||
|
@ -11,69 +11,71 @@ sub start_connection {
|
||||
my $self = shift;
|
||||
|
||||
#reset info
|
||||
$self->{_connection} = Qpsmtpd::Connection->new(); #reset connection
|
||||
$self->{_connection} = Qpsmtpd::Connection->new(); #reset connection
|
||||
$self->reset_transaction;
|
||||
$self->SUPER::start_connection(@_);
|
||||
}
|
||||
|
||||
sub read_input {
|
||||
my $self = shift;
|
||||
my $self = shift;
|
||||
|
||||
my $timeout =
|
||||
$self->config('timeoutsmtpd') # qmail smtpd control file
|
||||
|| $self->config('timeout') # qpsmtpd control file
|
||||
|| 1200; # default value
|
||||
my $timeout = $self->config('timeoutsmtpd') # qmail smtpd control file
|
||||
|| $self->config('timeout') # qpsmtpd control file
|
||||
|| 1200; # default value
|
||||
|
||||
alarm $timeout;
|
||||
eval {
|
||||
while (<STDIN>) {
|
||||
alarm 0;
|
||||
$_ =~ s/\r?\n$//s; # advanced chomp
|
||||
$self->log(LOGINFO, "dispatching $_");
|
||||
$self->connection->notes('original_string', $_);
|
||||
defined $self->dispatch(split / +/, $_, 2)
|
||||
or $self->respond(502, "command unrecognized: '$_'");
|
||||
alarm $timeout;
|
||||
alarm $timeout;
|
||||
eval {
|
||||
while (<STDIN>) {
|
||||
alarm 0;
|
||||
$_ =~ s/\r?\n$//s; # advanced chomp
|
||||
$self->log(LOGINFO, "dispatching $_");
|
||||
$self->connection->notes('original_string', $_);
|
||||
defined $self->dispatch(split / +/, $_, 2)
|
||||
or $self->respond(502, "command unrecognized: '$_'");
|
||||
alarm $timeout;
|
||||
}
|
||||
unless ($self->connection->notes('disconnected')) {
|
||||
$self->reset_transaction;
|
||||
$self->run_hooks('disconnect');
|
||||
$self->connection->notes(disconnected => 1);
|
||||
}
|
||||
};
|
||||
if ($@ =~ /^disconnect_tcpserver/) {
|
||||
die "disconnect_tcpserver";
|
||||
}
|
||||
unless ($self->connection->notes('disconnected')) {
|
||||
$self->reset_transaction;
|
||||
$self->run_hooks('disconnect');
|
||||
$self->connection->notes(disconnected => 1);
|
||||
else {
|
||||
$self->run_hooks("post-connection");
|
||||
$self->connection->reset;
|
||||
die "died while reading from STDIN (probably broken sender) - $@";
|
||||
}
|
||||
};
|
||||
if ($@ =~ /^disconnect_tcpserver/) {
|
||||
die "disconnect_tcpserver";
|
||||
} else {
|
||||
$self->run_hooks("post-connection");
|
||||
$self->connection->reset;
|
||||
die "died while reading from STDIN (probably broken sender) - $@";
|
||||
}
|
||||
alarm(0);
|
||||
alarm(0);
|
||||
}
|
||||
|
||||
sub respond {
|
||||
my ($self, $code, @messages) = @_;
|
||||
my ($self, $code, @messages) = @_;
|
||||
|
||||
if ( !$self->check_socket() ) {
|
||||
$self->log(LOGERROR, "Lost connection to client, cannot send response.");
|
||||
return(0);
|
||||
}
|
||||
if (!$self->check_socket()) {
|
||||
$self->log(LOGERROR,
|
||||
"Lost connection to client, cannot send response.");
|
||||
return (0);
|
||||
}
|
||||
|
||||
while (my $msg = shift @messages) {
|
||||
my $line = $code . (@messages?"-":" ").$msg;
|
||||
$self->log(LOGINFO, $line);
|
||||
print "$line\r\n" or ($self->log(LOGERROR, "Could not print [$line]: $!"), return 0);
|
||||
}
|
||||
return 1;
|
||||
while (my $msg = shift @messages) {
|
||||
my $line = $code . (@messages ? "-" : " ") . $msg;
|
||||
$self->log(LOGINFO, $line);
|
||||
print "$line\r\n"
|
||||
or ($self->log(LOGERROR, "Could not print [$line]: $!"), return 0);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub disconnect {
|
||||
my $self = shift;
|
||||
$self->log(LOGINFO,"click, disconnecting");
|
||||
$self->SUPER::disconnect(@_);
|
||||
$self->run_hooks("post-connection");
|
||||
$self->connection->reset;
|
||||
die "disconnect_tcpserver";
|
||||
my $self = shift;
|
||||
$self->log(LOGINFO, "click, disconnecting");
|
||||
$self->SUPER::disconnect(@_);
|
||||
$self->run_hooks("post-connection");
|
||||
$self->connection->reset;
|
||||
die "disconnect_tcpserver";
|
||||
}
|
||||
|
||||
1;
|
||||
|
@ -15,13 +15,13 @@ use Time::HiRes qw(gettimeofday);
|
||||
sub new { start(@_) }
|
||||
|
||||
sub start {
|
||||
my $proto = shift;
|
||||
my $class = ref($proto) || $proto;
|
||||
my %args = @_;
|
||||
my $proto = shift;
|
||||
my $class = ref($proto) || $proto;
|
||||
my %args = @_;
|
||||
|
||||
my $self = { _rcpt => [], started => time, };
|
||||
bless ($self, $class);
|
||||
return $self;
|
||||
my $self = {_rcpt => [], started => time,};
|
||||
bless($self, $class);
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub add_recipient {
|
||||
@ -30,27 +30,28 @@ sub add_recipient {
|
||||
}
|
||||
|
||||
sub remove_recipient {
|
||||
my ($self,$rcpt) = @_;
|
||||
$self->{_recipients} = [grep {$_->address ne $rcpt->address}
|
||||
@{$self->{_recipients} || []}] if $rcpt;
|
||||
my ($self, $rcpt) = @_;
|
||||
$self->{_recipients} =
|
||||
[grep { $_->address ne $rcpt->address } @{$self->{_recipients} || []}]
|
||||
if $rcpt;
|
||||
}
|
||||
|
||||
sub recipients {
|
||||
my $self = shift;
|
||||
@_ and $self->{_recipients} = [@_];
|
||||
($self->{_recipients} ? @{$self->{_recipients}} : ());
|
||||
my $self = shift;
|
||||
@_ and $self->{_recipients} = [@_];
|
||||
($self->{_recipients} ? @{$self->{_recipients}} : ());
|
||||
}
|
||||
|
||||
sub sender {
|
||||
my $self = shift;
|
||||
@_ and $self->{_sender} = shift;
|
||||
$self->{_sender};
|
||||
my $self = shift;
|
||||
@_ and $self->{_sender} = shift;
|
||||
$self->{_sender};
|
||||
}
|
||||
|
||||
sub header {
|
||||
my $self = shift;
|
||||
@_ and $self->{_header} = shift;
|
||||
$self->{_header};
|
||||
my $self = shift;
|
||||
@_ and $self->{_header} = shift;
|
||||
$self->{_header};
|
||||
}
|
||||
|
||||
# blocked() will return when we actually can do something useful with it...
|
||||
@ -63,32 +64,33 @@ sub header {
|
||||
#}
|
||||
|
||||
sub notes {
|
||||
my ($self,$key) = (shift,shift);
|
||||
# Check for any additional arguments passed by the caller -- including undef
|
||||
return $self->{_notes}->{$key} unless @_;
|
||||
return $self->{_notes}->{$key} = shift;
|
||||
my ($self, $key) = (shift, shift);
|
||||
|
||||
# Check for any additional arguments passed by the caller -- including undef
|
||||
return $self->{_notes}->{$key} unless @_;
|
||||
return $self->{_notes}->{$key} = shift;
|
||||
}
|
||||
|
||||
sub set_body_start {
|
||||
my $self = shift;
|
||||
$self->{_body_start} = $self->body_current_pos;
|
||||
if ($self->{_body_file}) {
|
||||
$self->{_header_size} = $self->{_body_start};
|
||||
$self->{_header_size} = $self->{_body_start};
|
||||
}
|
||||
else {
|
||||
$self->{_header_size} = 0;
|
||||
if ($self->{_body_array}) {
|
||||
foreach my $line (@{ $self->{_body_array} }) {
|
||||
foreach my $line (@{$self->{_body_array}}) {
|
||||
$self->{_header_size} += length($line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub body_start {
|
||||
my $self = shift;
|
||||
@_ and die "body_start now read only";
|
||||
$self->{_body_start};
|
||||
my $self = shift;
|
||||
@_ and die "body_start now read only";
|
||||
$self->{_body_start};
|
||||
}
|
||||
|
||||
sub body_current_pos {
|
||||
@ -100,110 +102,116 @@ sub body_current_pos {
|
||||
}
|
||||
|
||||
sub body_filename {
|
||||
my $self = shift;
|
||||
$self->body_spool() unless $self->{_filename};
|
||||
$self->{_body_file}->flush(); # so contents won't be cached
|
||||
return $self->{_filename};
|
||||
my $self = shift;
|
||||
$self->body_spool() unless $self->{_filename};
|
||||
$self->{_body_file}->flush(); # so contents won't be cached
|
||||
return $self->{_filename};
|
||||
}
|
||||
|
||||
sub body_spool {
|
||||
my $self = shift;
|
||||
$self->log(LOGINFO, "spooling message to disk");
|
||||
$self->{_filename} = $self->temp_file();
|
||||
$self->{_body_file} = IO::File->new($self->{_filename}, O_RDWR|O_CREAT, 0600)
|
||||
or die "Could not open file $self->{_filename} - $! "; # . $self->{_body_file}->error;
|
||||
if ($self->{_body_array}) {
|
||||
foreach my $line (@{ $self->{_body_array} }) {
|
||||
$self->{_body_file}->print($line) or die "Cannot print to temp file: $!";
|
||||
my $self = shift;
|
||||
$self->log(LOGINFO, "spooling message to disk");
|
||||
$self->{_filename} = $self->temp_file();
|
||||
$self->{_body_file} =
|
||||
IO::File->new($self->{_filename}, O_RDWR | O_CREAT, 0600)
|
||||
or die "Could not open file $self->{_filename} - $! "
|
||||
; # . $self->{_body_file}->error;
|
||||
if ($self->{_body_array}) {
|
||||
foreach my $line (@{$self->{_body_array}}) {
|
||||
$self->{_body_file}->print($line)
|
||||
or die "Cannot print to temp file: $!";
|
||||
}
|
||||
$self->{_body_start} = $self->{_header_size};
|
||||
}
|
||||
$self->{_body_start} = $self->{_header_size};
|
||||
}
|
||||
else {
|
||||
$self->log(LOGERROR, "no message body");
|
||||
}
|
||||
$self->{_body_array} = undef;
|
||||
else {
|
||||
$self->log(LOGERROR, "no message body");
|
||||
}
|
||||
$self->{_body_array} = undef;
|
||||
}
|
||||
|
||||
sub body_write {
|
||||
my $self = shift;
|
||||
my $data = shift;
|
||||
if ($self->{_body_file}) {
|
||||
#warn("body_write to file\n");
|
||||
# go to the end of the file
|
||||
seek($self->{_body_file},0,2)
|
||||
unless $self->{_body_file_writing};
|
||||
$self->{_body_file_writing} = 1;
|
||||
$self->{_body_file}->print(ref $data eq "SCALAR" ? $$data : $data)
|
||||
and $self->{_body_size} += length (ref $data eq "SCALAR" ? $$data : $data);
|
||||
}
|
||||
else {
|
||||
#warn("body_write to array\n");
|
||||
$self->{_body_array} ||= [];
|
||||
my $ref = ref($data) eq "SCALAR" ? $data : \$data;
|
||||
pos($$ref) = 0;
|
||||
while ($$ref =~ m/\G(.*?\n)/gc) {
|
||||
push @{ $self->{_body_array} }, $1;
|
||||
$self->{_body_size} += length($1);
|
||||
++$self->{_body_current_pos};
|
||||
my $self = shift;
|
||||
my $data = shift;
|
||||
if ($self->{_body_file}) {
|
||||
|
||||
#warn("body_write to file\n");
|
||||
# go to the end of the file
|
||||
seek($self->{_body_file}, 0, 2)
|
||||
unless $self->{_body_file_writing};
|
||||
$self->{_body_file_writing} = 1;
|
||||
$self->{_body_file}->print(ref $data eq "SCALAR" ? $$data : $data)
|
||||
and $self->{_body_size} +=
|
||||
length(ref $data eq "SCALAR" ? $$data : $data);
|
||||
}
|
||||
if ($$ref =~ m/\G(.+)\z/gc) {
|
||||
push @{ $self->{_body_array} }, $1;
|
||||
$self->{_body_size} += length($1);
|
||||
++$self->{_body_current_pos};
|
||||
else {
|
||||
#warn("body_write to array\n");
|
||||
$self->{_body_array} ||= [];
|
||||
my $ref = ref($data) eq "SCALAR" ? $data : \$data;
|
||||
pos($$ref) = 0;
|
||||
while ($$ref =~ m/\G(.*?\n)/gc) {
|
||||
push @{$self->{_body_array}}, $1;
|
||||
$self->{_body_size} += length($1);
|
||||
++$self->{_body_current_pos};
|
||||
}
|
||||
if ($$ref =~ m/\G(.+)\z/gc) {
|
||||
push @{$self->{_body_array}}, $1;
|
||||
$self->{_body_size} += length($1);
|
||||
++$self->{_body_current_pos};
|
||||
}
|
||||
$self->body_spool if ($self->{_body_size} >= $self->size_threshold());
|
||||
}
|
||||
$self->body_spool if ( $self->{_body_size} >= $self->size_threshold() );
|
||||
}
|
||||
}
|
||||
|
||||
sub body_size { # depreceated, use data_size() instead
|
||||
my $self = shift;
|
||||
$self->log(LOGWARN, "WARNING: body_size() is depreceated, use data_size() instead");
|
||||
$self->{_body_size} || 0;
|
||||
sub body_size { # depreceated, use data_size() instead
|
||||
my $self = shift;
|
||||
$self->log(LOGWARN,
|
||||
"WARNING: body_size() is depreceated, use data_size() instead");
|
||||
$self->{_body_size} || 0;
|
||||
}
|
||||
|
||||
sub data_size {
|
||||
shift->{_body_size} || 0;
|
||||
shift->{_body_size} || 0;
|
||||
}
|
||||
|
||||
sub body_length {
|
||||
my $self = shift;
|
||||
$self->{_body_size} or return 0;
|
||||
$self->{_header_size} or return 0;
|
||||
return $self->{_body_size} - $self->{_header_size};
|
||||
my $self = shift;
|
||||
$self->{_body_size} or return 0;
|
||||
$self->{_header_size} or return 0;
|
||||
return $self->{_body_size} - $self->{_header_size};
|
||||
}
|
||||
|
||||
sub body_resetpos {
|
||||
my $self = shift;
|
||||
my $self = shift;
|
||||
|
||||
if ($self->{_body_file}) {
|
||||
my $start = $self->{_body_start} || 0;
|
||||
seek($self->{_body_file}, $start, 0);
|
||||
$self->{_body_file_writing} = 0;
|
||||
}
|
||||
else {
|
||||
$self->{_body_current_pos} = $self->{_body_start};
|
||||
}
|
||||
if ($self->{_body_file}) {
|
||||
my $start = $self->{_body_start} || 0;
|
||||
seek($self->{_body_file}, $start, 0);
|
||||
$self->{_body_file_writing} = 0;
|
||||
}
|
||||
else {
|
||||
$self->{_body_current_pos} = $self->{_body_start};
|
||||
}
|
||||
|
||||
1;
|
||||
1;
|
||||
}
|
||||
|
||||
sub body_getline {
|
||||
my $self = shift;
|
||||
if ($self->{_body_file}) {
|
||||
my $start = $self->{_body_start} || 0;
|
||||
seek($self->{_body_file}, $start,0)
|
||||
if $self->{_body_file_writing};
|
||||
$self->{_body_file_writing} = 0;
|
||||
my $line = $self->{_body_file}->getline;
|
||||
return $line;
|
||||
}
|
||||
else {
|
||||
return unless $self->{_body_array};
|
||||
$self->{_body_current_pos} ||= 0;
|
||||
my $line = $self->{_body_array}->[$self->{_body_current_pos}];
|
||||
$self->{_body_current_pos}++;
|
||||
return $line;
|
||||
}
|
||||
my $self = shift;
|
||||
if ($self->{_body_file}) {
|
||||
my $start = $self->{_body_start} || 0;
|
||||
seek($self->{_body_file}, $start, 0)
|
||||
if $self->{_body_file_writing};
|
||||
$self->{_body_file_writing} = 0;
|
||||
my $line = $self->{_body_file}->getline;
|
||||
return $line;
|
||||
}
|
||||
else {
|
||||
return unless $self->{_body_array};
|
||||
$self->{_body_current_pos} ||= 0;
|
||||
my $line = $self->{_body_array}->[$self->{_body_current_pos}];
|
||||
$self->{_body_current_pos}++;
|
||||
return $line;
|
||||
}
|
||||
}
|
||||
|
||||
sub body_as_string {
|
||||
@ -218,55 +226,59 @@ sub body_as_string {
|
||||
}
|
||||
|
||||
sub body_fh {
|
||||
return shift->{_body_file};
|
||||
return shift->{_body_file};
|
||||
}
|
||||
|
||||
sub dup_body_fh {
|
||||
my ($self) = @_;
|
||||
open(my $fh, '<&=', $self->body_fh);
|
||||
return $fh;
|
||||
my ($self) = @_;
|
||||
open(my $fh, '<&=', $self->body_fh);
|
||||
return $fh;
|
||||
}
|
||||
|
||||
sub DESTROY {
|
||||
my $self = shift;
|
||||
# would we save some disk flushing if we unlinked the file before
|
||||
# closing it?
|
||||
my $self = shift;
|
||||
|
||||
$self->log(LOGDEBUG, sprintf( "DESTROY called by %s, %s, %s", (caller) ) );
|
||||
# would we save some disk flushing if we unlinked the file before
|
||||
# closing it?
|
||||
|
||||
if ( $self->{_body_file} ) {
|
||||
$self->log(LOGDEBUG, sprintf("DESTROY called by %s, %s, %s", (caller)));
|
||||
|
||||
if ($self->{_body_file}) {
|
||||
undef $self->{_body_file};
|
||||
};
|
||||
}
|
||||
|
||||
if ($self->{_filename} and -e $self->{_filename}) {
|
||||
if ( unlink $self->{_filename} ) {
|
||||
$self->log(LOGDEBUG, "unlinked ", $self->{_filename} );
|
||||
if (unlink $self->{_filename}) {
|
||||
$self->log(LOGDEBUG, "unlinked ", $self->{_filename});
|
||||
}
|
||||
else {
|
||||
$self->log(LOGERROR, "Could not unlink ", $self->{_filename}, ": $!");
|
||||
$self->log(LOGERROR, "Could not unlink ",
|
||||
$self->{_filename}, ": $!");
|
||||
}
|
||||
}
|
||||
|
||||
# These may not exist
|
||||
if ( $self->{_temp_files} ) {
|
||||
$self->log(LOGDEBUG, "Cleaning up temporary transaction files");
|
||||
foreach my $file ( @{$self->{_temp_files}} ) {
|
||||
next unless -e $file;
|
||||
unlink $file or $self->log(LOGERROR,
|
||||
"Could not unlink temporary file", $file, ": $!");
|
||||
# These may not exist
|
||||
if ($self->{_temp_files}) {
|
||||
$self->log(LOGDEBUG, "Cleaning up temporary transaction files");
|
||||
foreach my $file (@{$self->{_temp_files}}) {
|
||||
next unless -e $file;
|
||||
unlink $file
|
||||
or $self->log(LOGERROR, "Could not unlink temporary file",
|
||||
$file, ": $!");
|
||||
}
|
||||
}
|
||||
}
|
||||
# Ditto
|
||||
if ( $self->{_temp_dirs} ) {
|
||||
eval {use File::Path};
|
||||
$self->log(LOGDEBUG, "Cleaning up temporary directories");
|
||||
foreach my $dir ( @{$self->{_temp_dirs}} ) {
|
||||
rmtree($dir) or $self->log(LOGERROR,
|
||||
"Could not unlink temporary dir", $dir, ": $!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Ditto
|
||||
if ($self->{_temp_dirs}) {
|
||||
eval { use File::Path };
|
||||
$self->log(LOGDEBUG, "Cleaning up temporary directories");
|
||||
foreach my $dir (@{$self->{_temp_dirs}}) {
|
||||
rmtree($dir)
|
||||
or $self->log(LOGERROR, "Could not unlink temporary dir",
|
||||
$dir, ": $!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
@ -11,5 +11,4 @@ sub tildeexp {
|
||||
return $path;
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
|
@ -9,11 +9,17 @@ use Qpsmtpd::Constants;
|
||||
use Test::Qpsmtpd::Plugin;
|
||||
|
||||
sub new_conn {
|
||||
ok(my $smtpd = __PACKAGE__->new(), "new");
|
||||
ok(my $conn = $smtpd->start_connection(remote_host => 'localhost',
|
||||
remote_ip => '127.0.0.1'), "start_connection");
|
||||
is(($smtpd->response)[0], "220", "greetings");
|
||||
($smtpd, $conn);
|
||||
ok(my $smtpd = __PACKAGE__->new(), "new");
|
||||
ok(
|
||||
my $conn =
|
||||
$smtpd->start_connection(
|
||||
remote_host => 'localhost',
|
||||
remote_ip => '127.0.0.1'
|
||||
),
|
||||
"start_connection"
|
||||
);
|
||||
is(($smtpd->response)[0], "220", "greetings");
|
||||
($smtpd, $conn);
|
||||
}
|
||||
|
||||
sub start_connection {
|
||||
@ -24,11 +30,13 @@ sub start_connection {
|
||||
my $remote_info = "test\@$remote_host";
|
||||
my $remote_ip = $args{remote_ip} or croak "no remote_ip parameter";
|
||||
|
||||
my $conn = $self->SUPER::connection->start(remote_info => $remote_info,
|
||||
remote_ip => $remote_ip,
|
||||
remote_host => $remote_host,
|
||||
@_);
|
||||
|
||||
my $conn =
|
||||
$self->SUPER::connection->start(
|
||||
remote_info => $remote_info,
|
||||
remote_ip => $remote_ip,
|
||||
remote_host => $remote_host,
|
||||
@_
|
||||
);
|
||||
|
||||
$self->load_plugins;
|
||||
|
||||
@ -39,33 +47,33 @@ sub start_connection {
|
||||
}
|
||||
|
||||
sub respond {
|
||||
my $self = shift;
|
||||
$self->{_response} = [@_];
|
||||
my $self = shift;
|
||||
$self->{_response} = [@_];
|
||||
}
|
||||
|
||||
sub response {
|
||||
my $self = shift;
|
||||
$self->{_response} ? (@{ delete $self->{_response} }) : ();
|
||||
my $self = shift;
|
||||
$self->{_response} ? (@{delete $self->{_response}}) : ();
|
||||
}
|
||||
|
||||
sub command {
|
||||
my ($self, $command) = @_;
|
||||
$self->input($command);
|
||||
$self->response;
|
||||
my ($self, $command) = @_;
|
||||
$self->input($command);
|
||||
$self->response;
|
||||
}
|
||||
|
||||
sub input {
|
||||
my $self = shift;
|
||||
my $command = shift;
|
||||
my $self = shift;
|
||||
my $command = shift;
|
||||
|
||||
my $timeout = $self->config('timeout');
|
||||
alarm $timeout;
|
||||
my $timeout = $self->config('timeout');
|
||||
alarm $timeout;
|
||||
|
||||
$command =~ s/\r?\n$//s; # advanced chomp
|
||||
$self->log(LOGDEBUG, "dispatching $command");
|
||||
defined $self->dispatch(split / +/, $command, 2)
|
||||
$command =~ s/\r?\n$//s; # advanced chomp
|
||||
$self->log(LOGDEBUG, "dispatching $command");
|
||||
defined $self->dispatch(split / +/, $command, 2)
|
||||
or $self->respond(502, "command unrecognized: '$command'");
|
||||
alarm $timeout;
|
||||
alarm $timeout;
|
||||
}
|
||||
|
||||
sub config_dir {
|
||||
@ -95,6 +103,7 @@ sub run_plugin_tests {
|
||||
my $self = shift;
|
||||
$self->{_test_mode} = 1;
|
||||
my @plugins = $self->load_plugins();
|
||||
|
||||
# First count test number
|
||||
my $num_tests = 0;
|
||||
foreach my $plugin (@plugins) {
|
||||
@ -105,7 +114,7 @@ sub run_plugin_tests {
|
||||
require Test::Builder;
|
||||
my $Test = Test::Builder->new();
|
||||
|
||||
$Test->plan( tests => $num_tests );
|
||||
$Test->plan(tests => $num_tests);
|
||||
|
||||
# Now run them
|
||||
|
||||
|
@ -11,14 +11,16 @@ use Qpsmtpd::Constants;
|
||||
use Test::More;
|
||||
|
||||
sub register_tests {
|
||||
|
||||
# Virtual base method - implement in plugin
|
||||
}
|
||||
|
||||
sub register_test {
|
||||
my ($plugin, $test, $num_tests) = @_;
|
||||
$num_tests = 1 unless defined($num_tests);
|
||||
|
||||
# print STDERR "Registering test $test ($num_tests)\n";
|
||||
push @{$plugin->{_tests}}, { name => $test, num => $num_tests };
|
||||
push @{$plugin->{_tests}}, {name => $test, num => $num_tests};
|
||||
}
|
||||
|
||||
sub total_tests {
|
||||
@ -34,14 +36,15 @@ sub run_tests {
|
||||
my ($plugin, $qp) = @_;
|
||||
foreach my $t (@{$plugin->{_tests}}) {
|
||||
my $method = $t->{name};
|
||||
print "# Running $method tests for plugin " . $plugin->plugin_name . "\n";
|
||||
print "# Running $method tests for plugin "
|
||||
. $plugin->plugin_name . "\n";
|
||||
local $plugin->{_qp} = $qp;
|
||||
$plugin->$method();
|
||||
}
|
||||
}
|
||||
|
||||
sub validate_password {
|
||||
my ( $self, %a ) = @_;
|
||||
my ($self, %a) = @_;
|
||||
|
||||
my ($pkg, $file, $line) = caller();
|
||||
|
||||
@ -53,42 +56,42 @@ sub validate_password {
|
||||
my $ticket = $a{ticket};
|
||||
my $deny = $a{deny} || DENY;
|
||||
|
||||
if ( ! $src_crypt && ! $src_clear ) {
|
||||
if (!$src_crypt && !$src_clear) {
|
||||
$self->log(LOGINFO, "fail: missing password");
|
||||
return ( $deny, "$file - no such user" );
|
||||
};
|
||||
|
||||
if ( ! $src_clear && $method =~ /CRAM-MD5/i ) {
|
||||
$self->log(LOGINFO, "skip: cram-md5 not supported w/o clear pass");
|
||||
return ( DECLINED, $file );
|
||||
return ($deny, "$file - no such user");
|
||||
}
|
||||
|
||||
if ( defined $attempt_clear ) {
|
||||
if ( $src_clear && $src_clear eq $attempt_clear ) {
|
||||
if (!$src_clear && $method =~ /CRAM-MD5/i) {
|
||||
$self->log(LOGINFO, "skip: cram-md5 not supported w/o clear pass");
|
||||
return (DECLINED, $file);
|
||||
}
|
||||
|
||||
if (defined $attempt_clear) {
|
||||
if ($src_clear && $src_clear eq $attempt_clear) {
|
||||
$self->log(LOGINFO, "pass: clear match");
|
||||
return ( OK, $file );
|
||||
};
|
||||
|
||||
if ( $src_crypt && $src_crypt eq crypt( $attempt_clear, $src_crypt ) ) {
|
||||
$self->log(LOGINFO, "pass: crypt match");
|
||||
return ( OK, $file );
|
||||
return (OK, $file);
|
||||
}
|
||||
};
|
||||
|
||||
if ( defined $attempt_hash && $src_clear ) {
|
||||
if ( ! $ticket ) {
|
||||
if ($src_crypt && $src_crypt eq crypt($attempt_clear, $src_crypt)) {
|
||||
$self->log(LOGINFO, "pass: crypt match");
|
||||
return (OK, $file);
|
||||
}
|
||||
}
|
||||
|
||||
if (defined $attempt_hash && $src_clear) {
|
||||
if (!$ticket) {
|
||||
$self->log(LOGERROR, "skip: missing ticket");
|
||||
return ( DECLINED, $file );
|
||||
};
|
||||
return (DECLINED, $file);
|
||||
}
|
||||
|
||||
if ( $attempt_hash eq hmac_md5_hex( $ticket, $src_clear ) ) {
|
||||
if ($attempt_hash eq hmac_md5_hex($ticket, $src_clear)) {
|
||||
$self->log(LOGINFO, "pass: hash match");
|
||||
return ( OK, $file );
|
||||
};
|
||||
};
|
||||
return (OK, $file);
|
||||
}
|
||||
}
|
||||
|
||||
$self->log(LOGINFO, "fail: wrong password");
|
||||
return ( $deny, "$file - wrong password" );
|
||||
};
|
||||
return ($deny, "$file - wrong password");
|
||||
}
|
||||
|
||||
1;
|
||||
|
Loading…
Reference in New Issue
Block a user