diff --git a/Changes b/Changes index 9faca8b..c208580 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,13 @@ 0.29 + New temp_file() and temp_dir() methods; when used by plugins, they create + a filename or directory which will last only as long as the current + transaction. Also created a spool_dir() method which checks/creates the + spool_dir when the application starts up. All three methods are also + available in the base class where the temp_* objects are not automatically + limited to the transaction's lifetime. (John Peacock) + Added Gavin Carr's greylisting plugin Renamed config/ to config.sample/ diff --git a/README.plugins b/README.plugins index 3752639..c862663 100644 --- a/README.plugins +++ b/README.plugins @@ -200,3 +200,32 @@ With the $Include stuff you order using the filename of the plugin.d file. So if you have a plugin called xyz but want it to come early on, you call it's config file 00_xyz, but that file still refers to the plugin called xyz. + +=head1 Temporary Files + +The temporary file and directory functions can be used for plugin specific +workfiles and will automatically be deleted at the end of the current +transaction. + +=over 4 + +=item temp_file() + +Returns a unique name of a file located in the default spool directory, but +does not open that file (i.e. it is the name not a file handle). + +=item temp_dir() + +Returns the name of a unique directory located in the default spool +directory, after creating the directory with 0700 rights. If you need a +directory with different rights (say for an antivirus daemon), you will +need to use the base function $self->qp->temp_dir() which takes a single +parameter for the permissions requested (see L for details). A +directory created like this will B be deleted when the transaction is +ended. + +=item spool_dir() + +Returns the configured system-wide spool directory. + +=back diff --git a/lib/Qpsmtpd.pm b/lib/Qpsmtpd.pm index dd745d9..9cc9e23 100644 --- a/lib/Qpsmtpd.pm +++ b/lib/Qpsmtpd.pm @@ -255,4 +255,52 @@ sub _register_hook { } } +sub spool_dir { + my $self = shift; + + unless ( $self->{_spool_dir} ) { # first time through + my $spool_dir = $self->config('spool_dir') + || Qpsmtpd::Utils::tildeexp('~/tmp/'); + + $spool_dir .= "/" unless ($spool_dir =~ m!/$!); + + $spool_dir =~ /^(.+)$/ or die "spool_dir not configured properly"; + $spool_dir = $1; # cleanse the taint + $self->{_spool_dir} = $spool_dir; + + # Make sure the spool dir has appropriate rights + if (-e $spool_dir) { + my $mode = (stat($spool_dir))[2]; + warn "Permissions on spool_dir $spool_dir are not 0700" if $mode & 07077; + } + + # And finally, create it if it doesn't already exist + -d $spool_dir or mkdir($spool_dir, 0700) + or die "Could not create spool_dir $spool_dir: $!"; + } + + return $self->{_spool_dir}; +} + +# For unique filenames. We write to a local tmp dir so we don't need +# to make them unpredictable. +my $transaction_counter = 0; + +sub temp_file { + my $self = shift; + my $filename = $self->spool_dir() + . join(":", time, $$, $transaction_counter++); + $filename =~ tr!A-Za-z0-9:/_-!!cd; + return $filename; +} + +sub temp_dir { + my $self = shift; + my $mask = shift || 0700; + my $dirname = $self->temp_file(); + -d $dirname or mkdir($dirname, $mask) + or die "Could not create temporary directory $dirname: $!"; + return $dirname; +} + 1; diff --git a/lib/Qpsmtpd/Plugin.pm b/lib/Qpsmtpd/Plugin.pm index 2225140..84482ce 100644 --- a/lib/Qpsmtpd/Plugin.pm +++ b/lib/Qpsmtpd/Plugin.pm @@ -53,6 +53,24 @@ sub connection { shift->qp->connection; } +sub spool_dir { + shift->qp->spool_dir; +} + +sub temp_file { + 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; +} + # plugin inheritance: # usage: # sub register { diff --git a/lib/Qpsmtpd/Transaction.pm b/lib/Qpsmtpd/Transaction.pm index 1b280fc..4663e54 100644 --- a/lib/Qpsmtpd/Transaction.pm +++ b/lib/Qpsmtpd/Transaction.pm @@ -7,10 +7,6 @@ use Qpsmtpd::Constants; use IO::File qw(O_RDWR O_CREAT); -# For unique filenames. We write to a local tmp dir so we don't need -# to make them unpredictable. -my $transaction_counter = 0; - sub new { start(@_) } sub start { @@ -71,22 +67,7 @@ sub body_write { my $self = shift; my $data = shift; unless ($self->{_body_file}) { - my $spool_dir = $self->config('spool_dir') ? $self->config('spool_dir') - : Qpsmtpd::Utils::tildeexp('~/tmp/'); - - $spool_dir .= "/" unless ($spool_dir =~ m!/$!); - - $spool_dir =~ /^(.+)$/ or die "spool_dir not configured properly"; - $spool_dir = $1; - - if (-e $spool_dir) { - my $mode = (stat($spool_dir))[2]; - die "Permissions on spool_dir $spool_dir are not 0700" if $mode & 07077; - } - - -d $spool_dir or mkdir($spool_dir, 0700) or die "Could not create spool_dir $spool_dir: $!"; - $self->{_filename} = $spool_dir . join(":", time, $$, $transaction_counter++); - $self->{_filename} =~ tr!A-Za-z0-9:/_-!!cd; + $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; } @@ -129,6 +110,25 @@ sub DESTROY { if ($self->{_filename} and -e $self->{_filename}) { unlink $self->{_filename} or $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, ": $!"); + } + } + # 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, ": $!"); + } + } } diff --git a/t/tempstuff.t b/t/tempstuff.t new file mode 100644 index 0000000..467e5d7 --- /dev/null +++ b/t/tempstuff.t @@ -0,0 +1,27 @@ +#!/usr/bin/perl -w +use Test::More qw(no_plan); +use File::Path; +use strict; +use lib 't'; +use_ok('Test::Qpsmtpd'); + +BEGIN { # need this to happen before anything else + my $cwd = `pwd`; + chomp($cwd); + open my $spooldir, '>', "./config.sample/spool_dir"; + print $spooldir "$cwd/t/tmp"; + close $spooldir; +} + +ok(my ($smtpd, $conn) = Test::Qpsmtpd->new_conn(), "get new connection"); + +my ($spool_dir,$tempfile,$tempdir) = ( $smtpd->spool_dir, +$smtpd->temp_file(), $smtpd->temp_dir() ); + +ok( $spool_dir =~ m!t/tmp/$!, "Located the spool directory"); +ok( $tempfile =~ /^$spool_dir/, "Temporary filename" ); +ok( $tempdir =~ /^$spool_dir/, "Temporary directory" ); +ok( -d $tempdir, "And that directory exists" ); + +unlink "./config.sample/spool_dir"; +rmtree($spool_dir);