On 9/7/2011 2:13 PM, Mark Martinec wrote:
svn diff -c1165372
Thanks! That's the "new" command I was looking for. My old version of
svn doesn't support diff -c and I couldn't remember the -r<rev>:<rev>
syntax so I kept getting too much info.
I'll likely attach the patch to the bug for others to review.
regards,
KAM
Index: lib/Mail/SpamAssassin/Message.pm
===================================================================
--- lib/Mail/SpamAssassin/Message.pm (revision 1165371)
+++ lib/Mail/SpamAssassin/Message.pm (revision 1165372)
@@ -963,20 +963,26 @@
# up RAM with something we're not going to use?
#
if ($msg->{'type'} !~ m@^(?:text/(?:plain|html)$|message\b)@) {
- my $filepath;
- ($filepath, $msg->{'raw'}) = Mail::SpamAssassin::Util::secure_tmpfile();
-
- if ($filepath) {
+ my($filepath, $fh);
+ eval {
+ ($filepath, $fh) = Mail::SpamAssassin::Util::secure_tmpfile(); 1;
+ } or do {
+ my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
+ info("message: failed to create a temp file: %s", $eval_stat);
+ };
+ if ($fh) {
# The temp file was created, add it to the list of pending deletions
# we cannot just delete immediately in the POSIX idiom, as this is
# unportable (to win32 at least)
push @{$self->{tmpfiles}}, $filepath;
- $msg->{'raw'}->print(@{$body}) or die "error writing to $filepath: $!";
+ $fh->print(@{$body}) or die "error writing to $filepath: $!";
+ $msg->{'raw'} = $fh;
}
}
# if the part didn't get a temp file, go ahead and store the data in memory
- if (!exists $msg->{'raw'}) {
+ if (!defined $msg->{'raw'}) {
+ dbg("message: storing a body to memory");
$msg->{'raw'} = $body;
}
}
Index: lib/Mail/SpamAssassin/PerMsgStatus.pm
===================================================================
--- lib/Mail/SpamAssassin/PerMsgStatus.pm (revision 1165371)
+++ lib/Mail/SpamAssassin/PerMsgStatus.pm (revision 1165372)
@@ -2662,6 +2662,7 @@
}
my ($tmpf, $tmpfh) = Mail::SpamAssassin::Util::secure_tmpfile();
+ $tmpfh or die "failed to create a temporary file";
print $tmpfh $$fulltext or die "error writing to $tmpf: $!";
close $tmpfh or die "error closing $tmpf: $!";
Index: lib/Mail/SpamAssassin/Util.pm
===================================================================
--- lib/Mail/SpamAssassin/Util.pm (revision 1165371)
+++ lib/Mail/SpamAssassin/Util.pm (revision 1165372)
@@ -59,7 +59,7 @@
@EXPORT = ();
@EXPORT_OK = qw(&local_tz &base64_decode &untaint_var &untaint_file_path
&exit_status_str &proc_status_ok &am_running_on_windows
- &reverse_ip_address);
+ &reverse_ip_address &secure_tmpfile &secure_tmpdir);
}
use Mail::SpamAssassin;
@@ -1049,19 +1049,13 @@
sub secure_tmpfile {
my $tmpdir = untaint_file_path($ENV{'TMPDIR'} || File::Spec->tmpdir());
- if (!$tmpdir) {
- # Note: we would prefer to keep this fatal, as not being able to
- # find a writable tmpdir is a big deal for the calling code too.
- # That would be quite a psychotic case, also.
- warn "util: cannot find a temporary directory, set TMP or TMPDIR in
environment";
- return;
- }
+ defined $tmpdir && $tmpdir ne ''
+ or die "util: cannot find a temporary directory, set TMP or TMPDIR in
environment";
- opendir(my $dh, $tmpdir) || die "Could not open $tmpdir: $!";
- closedir $dh;
- my ($reportfile, $tmpfile);
- my $umask = umask 077;
+ opendir(my $dh, $tmpdir) or die "Could not open directory $tmpdir: $!";
+ closedir $dh or die "Error closing directory $tmpdir: $!";
+ my ($reportfile, $tmpfh);
for (my $retries = 20; $retries > 0; $retries--) {
# we do not rely on the obscurity of this name for security,
# we use a average-quality PRG since this is all we need
@@ -1071,36 +1065,34 @@
# instead, we require O_EXCL|O_CREAT to guarantee us proper
# ownership of our file, read the open(2) man page
- if (sysopen($tmpfile, $reportfile, O_RDWR|O_CREAT|O_EXCL, 0600)) {
- binmode $tmpfile or die "cannot set $reportfile to binmode: $!";
+ if (sysopen($tmpfh, $reportfile, O_RDWR|O_CREAT|O_EXCL, 0600)) {
+ binmode $tmpfh or die "cannot set $reportfile to binmode: $!";
last;
}
+ my $errno = $!;
- if ($!{EEXIST}) {
- # it is acceptable if $tmpfile already exists, try another
- next;
- }
-
- # error, maybe "out of quota" or "too many open files" (bug 4017)
- warn "util: secure_tmpfile failed to create file '$reportfile': $!\n";
-
# ensure the file handle is not semi-open in some way
- if ($tmpfile) {
- if (! close $tmpfile) {
+ if ($tmpfh) {
+ if (! close $tmpfh) {
info("error closing $reportfile: $!");
- $tmpfile=undef;
+ undef $tmpfh;
}
}
- }
- umask $umask;
+ # it is acceptable if $tmpfh already exists, try another
+ next if $errno == EEXIST;
- if (!$tmpfile) {
- warn "util: secure_tmpfile failed to create file, giving up";
- return; # undef
+ # error, maybe "out of quota", "too many open files", "Permission denied"
+ # (bug 4017); makes no sense retrying
+ die "util: failed to create a temporary file '$reportfile': $errno";
}
- return ($reportfile, $tmpfile);
+ if (!$tmpfh) {
+ warn "util: secure_tmpfile failed to create a temporary file, giving up";
+ return;
+ }
+
+ return ($reportfile, $tmpfh);
}
=item my ($dirpath) = secure_tmpdir();
Index: lib/Mail/SpamAssassin/Plugin/BodyRuleBaseExtractor.pm
===================================================================
--- lib/Mail/SpamAssassin/Plugin/BodyRuleBaseExtractor.pm (revision
1165371)
+++ lib/Mail/SpamAssassin/Plugin/BodyRuleBaseExtractor.pm (revision
1165372)
@@ -510,6 +510,7 @@
$rule =~ s/(?<!\\)(\w)\?/\($1\|\)/gs;
my ($tmpf, $tmpfh) = Mail::SpamAssassin::Util::secure_tmpfile();
+ $tmpfh or die "failed to create a temporary file";
untaint_var(\$tmpf);
# attempt to find a safe regexp delimiter...
Index: sa-update.raw
===================================================================
--- sa-update.raw (revision 1165371)
+++ sa-update.raw (revision 1165372)
@@ -94,6 +94,9 @@
# These should already be available
use Mail::SpamAssassin;
+use Mail::SpamAssassin::Util qw(untaint_var untaint_file_path
+ proc_status_ok exit_status_str am_running_on_windows
+ secure_tmpfile secure_tmpdir);
# Make the main dbg() accessible in our package w/o an extra function
*dbg=\&Mail::SpamAssassin::dbg;
@@ -142,7 +145,7 @@
'debug|D:s' => \$opt{'debug'},
'version|V' => \$opt{'version'},
'help|h|?' => \$opt{'help'},
- 'verbose|v' => \$opt{'verbose'},
+ 'verbose|v+' => \$opt{'verbose'},
'checkonly' => \$opt{'checkonly'},
'allowplugins' => \$opt{'allowplugins'},
'refreshmirrors' => \$opt{'refreshmirrors'},
@@ -210,7 +213,7 @@
});
if (defined $opt{'updatedir'}) {
- $opt{'updatedir'} =
Mail::SpamAssassin::Util::untaint_file_path($opt{'updatedir'});
+ $opt{'updatedir'} = untaint_file_path($opt{'updatedir'});
}
else {
$opt{'updatedir'} = $SA->sed_path('__local_state_dir__/__version__');
@@ -239,7 +242,7 @@
# we're not a setuid script, we trust them
foreach my $optkey (keys %opt) {
next if ref $opt{$optkey};
- Mail::SpamAssassin::Util::untaint_var(\$opt{$optkey});
+ untaint_var(\$opt{$optkey});
}
##############################################################################
@@ -261,9 +264,8 @@
if (defined $opt{'gpgkeyfile'}) {
$GPG_ENABLED = 1;
- unless (open(GPG, $opt{'gpgkeyfile'})) {
- die "Can't open ".$opt{'gpgkeyfile'}." for reading: $!\n";
- }
+ open(GPG, $opt{'gpgkeyfile'})
+ or die "cannot open $opt{'gpgkeyfile'} for reading: $!\n";
dbg("gpg: reading in gpgfile ".$opt{'gpgkeyfile'});
while(my $key = <GPG>) {
@@ -282,7 +284,7 @@
dbg("gpg: adding key id $key");
$valid_GPG{$key} = 1;
}
- close(GPG);
+ close(GPG) or die "cannot close $opt{'gpgkeyfile'}: $!";
}
# At this point, we need to know where GPG is ...
@@ -336,9 +338,8 @@
@channels = @{$opt{'channel'}};
}
if (defined $opt{'channelfile'}) {
- unless (open(CHAN, $opt{'channelfile'})) {
- die "Can't open ".$opt{'channelfile'}." for reading: $!\n";
- }
+ open(CHAN, $opt{'channelfile'})
+ or die "cannot open $opt{'channelfile'} for reading: $!\n";
dbg("channel: reading in channelfile ".$opt{'channelfile'});
@channels = ();
@@ -354,14 +355,14 @@
dbg("channel: adding $chan");
push(@channels, $chan);
}
- close(CHAN);
+ close(CHAN) or die "cannot close $opt{'channelfile'}: $!";
}
# untaint the channel listing
for(my $ind = 0; $ind < @channels; $ind++) {
local($1); # bug 5061: prevent random taint flagging of $1
if ($channels[$ind] =~ /^([a-zA-Z0-9._-]+)$/) {
- Mail::SpamAssassin::Util::untaint_var(\$channels[$ind]);
+ untaint_var(\$channels[$ind]);
}
else {
dbg("channel: skipping invalid channel: $channels[$ind]");
@@ -387,11 +388,12 @@
}
# Generate a temporary file to put channel content in for later use ...
-my ($content_file, $tfh) = Mail::SpamAssassin::Util::secure_tmpfile();
-if ( !defined $content_file ) {
- die "fatal: could not create temporary channel content file: $!\n";
-}
-close($tfh);
+my ($content_file, $tfh) = secure_tmpfile();
+$tfh
+ or die "fatal: could not create temporary channel content file: $!\n";
+close $tfh
+ or die "cannot close temporary channel content file $content_file: $!";
+undef $tfh;
my $lint_failures = 0;
my $channel_failures = 0;
@@ -443,7 +445,7 @@
$currentV = $1;
}
}
- close(CF);
+ close(CF) or die "cannot close $CFFile: $!";
}
my $newV;
@@ -507,7 +509,7 @@
# when we lint the site pre files, we might as well use the channel temp dir
dbg("channel: preparing temp directory for new channel");
if (!$UPDTmp) {
- $UPDTmp = Mail::SpamAssassin::Util::secure_tmpdir();
+ $UPDTmp = secure_tmpdir();
dbg("generic: update tmp directory $UPDTmp");
}
elsif (!clean_update_dir($UPDTmp)) {
@@ -547,7 +549,7 @@
local $/ = undef;
$mirby = <MIRBY>;
}
- close(MIRBY);
+ close(MIRBY) or die "cannot close $mirby_path: $!";
}
if (!defined $mirby) {
@@ -682,11 +684,15 @@
}
# Write the content out to a temp file for GPG/Archive::Tar interaction
- dbg("channel: populating temp content file");
- open(TMP, ">$content_file") || die "fatal: couldn't create content temp file
$content_file: $!\n";
- binmode TMP;
- print TMP $content || die "fatal: can't write to content temp file
$content_file: $!\n";
- close(TMP);
+ dbg("channel: populating temp content file %s", $content_file);
+ open(TMP, ">$content_file")
+ or die "fatal: cannot create content temp file $content_file: $!\n";
+ binmode TMP
+ or die "fatal: cannot set binmode on content temp file $content_file:
$!\n";
+ print TMP $content
+ or die "fatal: cannot write to content temp file $content_file: $!\n";
+ close TMP
+ or die "fatal: cannot close content temp file $content_file: $!\n";
# to sign : gpg -bas file
# to verify: gpg --verify --batch --no-tty --status-fd=1 -q --logger-fd=1
file.asc file
@@ -696,13 +702,16 @@
if ($GPG) {
dbg("gpg: populating temp signature file");
my $sig_file;
- ($sig_file, $tfh) = Mail::SpamAssassin::Util::secure_tmpfile();
- if ( !defined $sig_file ) {
- die "fatal: couldn't create temp file for GPG signature: $!\n";
- }
- binmode $tfh;
- print $tfh $GPG || die "fatal: can't write temp file for GPG signature:
$!\n";
- close($tfh);
+ ($sig_file, $tfh) = secure_tmpfile();
+ $tfh
+ or die "fatal: couldn't create temp file for GPG signature: $!\n";
+ binmode $tfh
+ or die "fatal: cannot set binmode on temp file for GPG signature: $!\n";
+ print $tfh $GPG
+ or die "fatal: cannot write temp file for GPG signature: $!\n";
+ close $tfh
+ or die "fatal: cannot close temp file for GPG signature: $!\n";
+ undef $tfh;
dbg("gpg: calling gpg");
@@ -714,7 +723,7 @@
"--no-tty --status-fd=1 -q --logger-fd=1";
unless (open(CMD, "$CMD $sig_file $content_file|")) {
- unlink $sig_file || warn "error: can't unlink $sig_file: $!\n";
+ unlink $sig_file or warn "error: cannot unlink $sig_file: $!\n";
die "fatal: couldn't execute $GPGPath: $!\n";
}
@@ -746,8 +755,8 @@
$signer = $key if (length $key > length $signer);
}
- close(CMD);
- unlink $sig_file || warn "Can't unlink $sig_file: $!\n";
+ close(CMD) or die "cannot close pipe to $GPGPath: $!";
+ unlink $sig_file or warn "cannot unlink $sig_file: $!\n";
if ($signer) {
my $keyid = substr $signer, -8;
@@ -837,10 +846,8 @@
else {
# create the dir, if it doesn't exist
dbg("channel: creating $UPDDir");
- if (!mkpath([$UPDDir], 0, 0777)) {
- warn "channel: can't create channel directory $UPDDir: $!\n";
- return 0;
- }
+ mkpath([$UPDDir], 0, 0777)
+ or die "channel: cannot create channel directory $UPDDir: $!\n";
# ok, that test worked. it's now likely that the .cf's will
# similarly be ok to rename, too. Too late to stop from here on
@@ -855,8 +862,10 @@
# bug 4941: try to get rid of the empty directories to avoid leaving SA
# with no rules.
- rmdir $UPDDir;
- rmdir $opt{'updatedir'};
+ rmdir $UPDDir
+ or dbg("channel: error removing dir %s: %s", $UPDDir, $!);
+ rmdir $opt{'updatedir'}
+ or dbg("channel: error removing dir %s: %s", $opt{'updatedir'}, $!);
},
},
@@ -891,13 +900,10 @@
# Write out the mirby file, not fatal if it doesn't work
dbg("channel: creating MIRRORED.BY file");
- if (open(MBY, ">$mirby_path")) {
- print MBY $mirby;
- close(MBY);
- }
- else {
- warn "error: can't write new MIRRORED.BY file: $!\n";
- }
+ open(MBY, ">$mirby_path")
+ or die "cannot create a new MIRRORED.BY file: $!\n";
+ print MBY $mirby or die "error writing to $mirby_path: $!";
+ close(MBY) or die "cannot close $mirby_path: $!";
return 1;
},
@@ -920,14 +926,13 @@
# Find all of the cf and pre files
- unless (opendir(DIR, $UPDDir)) {
- die "fatal: can't access $UPDDir: $!\n";
- }
+ opendir(DIR, $UPDDir)
+ or die "fatal: cannot access $UPDDir: $!\n";
my @files;
while(my $file = readdir(DIR)) {
next if $file eq '.' || $file eq '..';
- Mail::SpamAssassin::Util::untaint_var(\$file);
+ untaint_var(\$file);
my $path = File::Spec->catfile($UPDDir, $file);
next unless (-f $path); # shouldn't ever happen
push(@files, $file);
@@ -947,7 +952,7 @@
dbg("channel: adding $file");
}
- closedir(DIR);
+ closedir(DIR) or die "cannot close directory $UPDDir: $!";
# Finally, write out the files to include the update files
if (!write_channel_file($PREFile, \@PRE)) {
@@ -972,14 +977,22 @@
);
my $error;
+ my $eval_stat;
for(my $elem = 0; $elem <= $#totry; $elem++) {
- if (!&{$totry[$elem]->{'try'}}()) {
+ my $success;
+ eval {
+ $success = &{$totry[$elem]->{'try'}}(); 1;
+ } or do {
+ $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
+ };
+ if (!$success) {
$error = $elem;
+ $eval_stat = "step $elem unsuccessful" if !defined $eval_stat;
last;
}
}
if (defined $error) {
- dbg("channel: channel failed, attempting rollback");
+ dbg("channel: channel failed, attempting rollback: %s", $eval_stat);
for(my $elem = $error; $elem >= 0; $elem--) {
&{$totry[$elem]->{'rollback'}}();
}
@@ -1003,13 +1016,13 @@
next if $stat_errn == ENOENT;
if ($stat_errn != 0) {
- warn "error: can't access $_: $!\n";
+ warn "error: cannot access $_: $!\n";
}
elsif (-d _) {
- rmdir $_ || warn "error: can't remove directory $_: $!\n";
+ rmdir $_ or warn "error: cannot remove directory $_: $!\n";
}
elsif (-f _) {
- unlink $_ || warn "error: can't remove file $_: $!\n";
+ unlink $_ or warn "error: cannot remove file $_: $!\n";
}
else {
warn "error: '$_' isn't a file nor a directory, skipping\n";
@@ -1058,7 +1071,7 @@
my ($file) = @_;
open (IN, "<$file") or die "cannot open $file\n";
my $all = join('', <IN>);
- close IN;
+ close IN or die "cannot close $file: $!";
return $all;
}
@@ -1070,7 +1083,7 @@
return 1 unless @{$contents};
if (open(FILE, ">$filename")) {
- print FILE @{$contents};
+ print FILE @{$contents} or die "cannot write to $filename: $!";
close FILE or return 0;
return 1;
}
@@ -1093,10 +1106,10 @@
my $input = shift;
my $tfh = IO::Zlib->new($input, "rb");
- die "fatal: couldn't read content tmpfile $content_file: $!\n" unless $tfh;
+ $tfh or die "fatal: couldn't read content tmpfile $content_file: $!\n";
my $tar = Archive::Tar->new($tfh);
- die "fatal: couldn't create Archive::Tar object!\n" unless $tar;
+ $tar or die "fatal: couldn't open tar archive!\n";
# stupid Archive::Tar is not natively taint-safe! duh.
# return $tar->extract();
@@ -1113,7 +1126,10 @@
$outfname = File::Spec->catfile($todir, $outfname);
dbg "extracting: $outfname";
- if (open OUT, ">".$outfname) {
+ if (!open OUT, ">".$outfname) {
+ warn "error: failed to create $outfname: $!";
+ goto failed;
+ } else {
my $content = $tar->get_content($file);
if ($outfname =~ /\.(?:pre|cf)$/) {
@@ -1129,14 +1145,10 @@
}
}
- print OUT $content;
- if (!close OUT) {
- warn "error: write failed to $outfname: $!";
- goto failed;
- }
- } else {
- warn "error: failed to open $outfname for write: $!";
- goto failed;
+ print OUT $content
+ or do { warn "error writing to $outfname: $!"; goto failed };
+ close OUT
+ or do { warn "error: write failed to $outfname: $!"; goto failed }
}
}
@@ -1157,14 +1169,20 @@
if ($RR) {
foreach my $rr ($RR->answer) {
+ next if !$rr; # no answer records, only rcode
+ next if $rr->type ne 'TXT'; # only interested in TXT fields
my $text = $rr->rdatastr;
local($1);
$text =~ /^"(.*)"$/;
push @result, $1;
}
+ printf("DNS TXT query: %s -> %s\n", $query, join(", ",@result))
+ if $opt{'verbose'} && $opt{'verbose'} > 1;
}
else {
dbg("dns: query failed: $query => " . $res->errorstring);
+ printf("DNS TXT query %s failed: %s\n", $query, $res->errorstring)
+ if $opt{'verbose'} && $opt{'verbose'} > 1;
}
return @result;
@@ -1196,6 +1214,11 @@
$response = $ua->request($request);
+ printf("http: %sGET %s, %s\n",
+ defined $ims ? 'IMS ' : '', $url,
+ !$response ? '(no response)' : $response->status_line)
+ if $opt{'verbose'} && $opt{'verbose'} > 1;
+
if ($response->is_success) {
return $response->content;
}
@@ -1301,7 +1324,7 @@
my $gpghome = '';
if ($opt{'gpghomedir'}) {
$gpghome = $opt{'gpghomedir'};
- if (Mail::SpamAssassin::Util::am_running_on_windows()) {
+ if (am_running_on_windows()) {
# windows is single-quote-phobic; bug 4958 cmt 7
$gpghome =~ s/\"/\\\"/gs;
$gpghome = "--homedir=\"$gpghome\"";
@@ -1341,7 +1364,10 @@
}
}
- close(CMD);
+ my $errno = 0; close CMD or $errno = $!;
+ proc_status_ok($?,$errno)
+ or warn("gpg: process '$CMD' finished: ".exit_status_str($?,$errno)."\n");
+
return ($? >> 8);
}
@@ -1357,7 +1383,8 @@
dbg("gpg: importing default keyring to ".$opt{gpghomedir});
unless (-d $opt{gpghomedir}) {
# use 0700 to avoid "unsafe permissions" warning
- mkpath([$opt{'gpghomedir'}], 0, 0700) or die "cannot mkpath
$opt{gpghomedir}: $!";
+ mkpath([$opt{'gpghomedir'}], 0, 0700)
+ or die "cannot mkpath $opt{gpghomedir}: $!";
}
import_gpg_key($defkey);
}
@@ -1375,25 +1402,25 @@
my $dir = shift;
unless (opendir(DIR, $dir)) {
- warn "error: can't opendir $dir: $!\n";
+ warn "error: cannot opendir $dir: $!\n";
dbg("generic: attempt to opendir ($dir) failed");
return;
}
while(my $file = readdir(DIR)) {
next if $file eq '.' || $file eq '..';
- Mail::SpamAssassin::Util::untaint_var(\$file);
+ untaint_var(\$file);
my $path = File::Spec->catfile($dir, $file);
next unless (-f $path);
dbg("generic: unlinking $file");
if (!unlink $path) {
- warn "error: can't remove file $path: $!\n";
- closedir(DIR);
+ warn "error: cannot remove file $path: $!\n";
+ closedir(DIR) or die "cannot close directory $dir: $!";
return;
}
}
- closedir(DIR);
+ closedir(DIR) or die "cannot close directory $dir: $!";
return 1;
}
@@ -1463,7 +1490,8 @@
(default: @@LOCAL_STATE_DIR@@/@@VERSION@@)
--refreshmirrors Force the MIRRORED.BY file to be updated
-D, --debug [area=n,...] Print debugging messages
- -v, --verbose Be more verbose, like print updated channel names
+ -v, --verbose Be verbose, like print updated channel names;
+ For more verbosity specify multiple times
-V, --version Print version
-h, --help Print usage message