82
85
# Make the main dbg() accessible in our package w/o an extra function
83
86
*dbg=\&Mail::SpamAssassin::dbg;
85
89
# Clean up PATH appropriately
86
90
Mail::SpamAssassin::Util::clean_path_in_taint_mode();
88
92
# Default list of GPG keys allowed to sign update releases
90
# pub 1024D/265FA05B 2003-06-09 SpamAssassin Signing Key <release@spamassassin.org>
91
# Key fingerprint = 26C9 00A4 6DD4 0CD5 AD24 F6D7 DEE0 1987 265F A05B
92
# sub 1024D/FC51569B 2003-08-21
94
my %valid_GPG = ( '265FA05B' => 1 );
94
# pub 1024D/265FA05B 2003-06-09
95
# Key fingerprint = 26C9 00A4 6DD4 0CD5 AD24 F6D7 DEE0 1987 265F A05B
96
# uid SpamAssassin Signing Key <release@spamassassin.org>
97
# sub 1024D/FC51569B 2003-08-21
99
# pub 4096R/5244EC45 2005-12-20
100
# Key fingerprint = 5E54 1DC9 59CB 8BAC 7C78 DFDC 4056 A61A 5244 EC45
101
# uid updates.spamassassin.org Signing Key <release@spamassassin.org>
102
# sub 4096R/24F434CE 2005-12-20
104
# note for gpg newbs: these are "long" gpg keyids. It's common to also
105
# use the last 8 hex digits as a shorter keyid string.
108
'26C900A46DD40CD5AD24F6D7DEE01987265FA05B' => 1,
109
'0C2B1D7175B852C64B3CDC716C55397824F434CE' => 1,
110
'5E541DC959CB8BAC7C78DFDC4056A61A5244EC45' => 1,
96
113
# Default list of channels to update against
163
186
PREFIX => $PREFIX,
164
187
DEF_RULES_DIR => $DEF_RULES_DIR,
165
LOCAL_RULES_DIR => $LOCAL_RULES_DIR
188
LOCAL_RULES_DIR => $LOCAL_RULES_DIR,
189
LOCAL_STATE_DIR => $LOCAL_STATE_DIR,
167
$site_rules_path ||= $SA->first_existing_path(@Mail::SpamAssassin::site_rules_path);
192
if (defined $opt{'updatedir'}) {
193
$opt{'updatedir'} = Mail::SpamAssassin::Util::untaint_file_path($opt{'updatedir'});
196
$opt{'updatedir'} = $SA->sed_path('__local_state_dir__/spamassassin/__version__');
169
199
dbg("generic: sa-update version $VERSION");
170
dbg("generic: using update directory: $site_rules_path");
200
dbg("generic: using update directory: $opt{'updatedir'}");
172
202
# doesn't really display useful things for this script, but we do want
173
203
# a module/version listing, etc. sa-update may be used for older versions
252
if ( $opt{'import'} ) {
253
my $ex = import_gpg_key($opt{'import'});
257
# does the sa-update keyring exist? if not, import it
259
if(!-f File::Spec->catfile($opt{'gpghomedir'}, "secring.gpg")) {
260
import_default_keyring();
261
# attempt to continue even if this fails, anyway
265
# convert fingerprint gpg ids to keyids
266
foreach (keys %valid_GPG) {
267
my $id = substr $_, -8;
211
271
# Deal with channel-related options
212
if (defined $opt{'channel'}) {
213
push(@channels, @{$opt{'channel'}});
272
if (defined $opt{'channel'} && scalar @{$opt{'channel'}} > 0) {
273
@channels = @{$opt{'channel'}};
215
275
if (defined $opt{'channelfile'}) {
216
276
unless (open(CHAN, $opt{'channelfile'})) {
291
# untaint the channel listing
292
for(my $ind = 0; $ind < @channels; $ind++) {
293
if ($channels[$ind] =~ /^([a-zA-Z0-9._-]+)$/) {
294
$channels[$ind] = $1;
297
splice @channels, $ind, 1;
229
301
# find GPG in the PATH
231
302
if ($GPG_ENABLED) {
232
dbg("gpg: Searching for 'gpg' in ".$ENV{'PATH'});
233
foreach my $dir (split(/:/, $ENV{'PATH'})) {
234
$dir = File::Spec->catfile($dir, 'gpg');
240
die "fatal: couldn't find GPG in \$PATH\n" unless ($GPGPath);
241
dbg("gpg: found $GPGPath");
303
$GPGPath = find_gpg_path();
242
304
dbg("gpg: release trusted key id list: ".join(" ", keys %valid_GPG));
246
308
my $res = Net::DNS::Resolver->new();
248
my $ua = LWP::UserAgent->new;
310
my $ua = LWP::UserAgent->new();
249
311
$ua->agent("sa-update/$VERSION");
312
$ua->timeout(60); # a good long timeout; 10 is too short for Coral!
253
315
# Generate a temporary file to put channel content in for later use ...
254
316
my ($content_file, $tfh) = Mail::SpamAssassin::Util::secure_tmpfile();
319
# and another, for the new config file
320
my ($newcf_file, $tfh2) = Mail::SpamAssassin::Util::secure_tmpfile();
323
# by default, exit code is 1, to indicate no updates occurred
257
326
# Go ahead and loop through all of the channels
259
327
foreach my $channel (@channels) {
260
328
dbg("channel: attempting channel $channel");
313
# We don't currently have the list of mirrors, so go grab it.
314
unless (-f "$UPDDir/MIRRORED.BY") {
385
# ensure dirs exist, upfront
386
unless (-d $UPDDir) {
387
dbg("channel: creating $UPDDir");
388
mkpath([$UPDDir], 0, 0777) or die "fatal: can't create $UPDDir: $!\n";
390
unless (-d $UPDTmp) {
391
dbg("channel: creating $UPDTmp");
392
mkpath([$UPDTmp], 0, 0777) or die "fatal: can't create $UPDTmp: $!\n";
395
# copy the MIRRORED.BY file to the tmpdir, if it exists
396
if (-f "$UPDDir/MIRRORED.BY") {
397
unlink("$UPDTmp/MIRRORED.BY");
399
my ($x, $atime, $mtime);
400
($x,$x,$x,$x,$x,$x,$x,$x,$atime,$mtime,$x) = stat "$UPDDir/MIRRORED.BY";
402
copy("$UPDDir/MIRRORED.BY", "$UPDTmp/MIRRORED.BY")
403
or die "fatal: cannot copy $UPDDir/MIRRORED.BY to $UPDTmp/MIRRORED.BY";
405
# ensure modtimes match
406
utime($atime, $mtime, "$UPDTmp/MIRRORED.BY");
409
# We don't currently have the list of mirrors, so go grab it.
315
410
dbg("channel: no MIRRORED.BY file available");
316
411
my $mirror = do_txt_query("mirrors.$channel");
317
412
unless ($mirror) {
318
413
warn "error: no mirror data available for channel $channel\n";
319
dbg("channel: MIRRORED.BY file location was not in DNS, channel failed");
414
channel_failed("channel: MIRRORED.BY file location was not in DNS");
323
416
$mirror = http_get($mirror);
324
417
unless ($mirror) {
325
418
warn "error: no mirror data available for channel $channel\n";
326
dbg("channel: MIRRORED.BY contents were missing, channel failed");
419
channel_failed("channel: MIRRORED.BY contents were missing");
331
unless (-d $UPDDir) {
332
dbg("channel: creating $UPDDir");
333
mkdir $UPDDir || die "fatal: can't create $UPDDir: $!\n";
336
unless (open(MIR, ">$UPDDir/MIRRORED.BY")) {
423
unless (open(MIR, ">$UPDTmp/MIRRORED.BY")) {
337
424
warn "error: can't create mirrors file: $!\n";
338
dbg("channel: MIRRORED.BY creation failure, channel failed");
425
channel_failed("channel: MIRRORED.BY creation failure");
342
428
print MIR $mirror;
475
568
# Determine the fate of the signature
570
my $missingkeys = '';
477
571
while(my $GNUPG = <CMD>) {
478
next unless ($GNUPG =~ /^\Q[GNUPG:] GOODSIG\E \S+(\S{8})/);
575
if ($GNUPG =~ /^gpg: fatal:/) {
576
warn $GNUPG."\n"; # report bad news
579
if ($GNUPG =~ /^\Q[GNUPG:]\E NO_PUBKEY \S+(\S{8})$/) {
580
$missingkeys .= $1." ";
583
next unless ($GNUPG =~ /^\Q[GNUPG:]\E (?:VALID|GOOD)SIG (\S{8,40})/);
586
# we want either a keyid (8) or a fingerprint (40)
587
if (length $key > 8 && length $key < 40) {
588
substr($key, 8) = '';
591
# use the longest match we can find
592
$signer = $key if (length $key > length $signer);
483
596
unlink $sig_file || warn "Can't unlink $sig_file: $!\n";
486
dbg("gpg: good signature made by key id $signer");
599
my $keyid = substr $signer, -8;
600
dbg("gpg: found signature made by key $signer");
487
601
if (exists $valid_GPG{$signer}) {
488
602
dbg("gpg: key id $signer is release trusted");
604
elsif (exists $valid_GPG{$keyid}) {
605
dbg("gpg: key id $keyid is release trusted");
491
dbg("gpg: key id $signer is not release trusted");
608
dbg("gpg: key id $keyid is not release trusted");
496
613
unless ($signer) {
497
warn "error: GPG validation failed\n";
498
dbg("channel: GPG verification failed, channel failed");
614
warn "error: GPG validation failed!\n";
618
warn <<ENDOFVALIDATIONERR;
619
The update downloaded successfully, but it was not signed with a trusted GPG
620
key. Instead, it was signed with the following keys:
624
Perhaps you need to import the channel's GPG key? For example:
626
wget http://spamassassin.apache.org/updates/GPG.KEY
633
warn <<ENDOFVALIDATIONERR;
634
The update downloaded successfully, but the GPG signature verification
640
channel_failed("channel: GPG validation failed");
518
659
dbg("channel: cleaning out update directory");
519
unless (opendir(DIR, $UPDDir)) {
520
warn "error: can't readdir $UPDDir: $!\n";
521
dbg("channel: attempt to readdir failed, channel failed");
660
if (!clean_update_dir($UPDTmp)) {
661
channel_failed("channel: attempt to clean update dir failed");
525
while(my $file = readdir(DIR)) {
526
next unless (-f "$UPDDir/$file");
527
next if ($file eq 'MIRRORED.BY');
528
dbg("channel: unlinking $file");
529
unlink "$UPDDir/$file" || warn "error: can't remove file $UPDDir/$file: $!\n";
532
unlink "$UPDDir.cf" || warn "error: can't remove file $UPDDir.cf: $!\n";
665
unlink $CFFTmp || warn "error: can't remove file $CFFTmp: $!\n";
534
667
$tfh = IO::Zlib->new($content_file, "rb");
535
668
die "fatal: couldn't read content tmpfile $content_file: $!\n" unless $tfh;
537
670
my $tar = Archive::Tar->new($tfh);
538
671
die "fatal: couldn't create Archive::Tar object!\n" unless $tar;
540
# make sure we're doing the work in the update directory
541
unless (chdir $UPDDir) {
542
warn "error: can't chdir into $UPDDir: $!\n";
543
dbg("channel: chdir failed, channel failed");
548
673
dbg("channel: extracting archive");
549
unless ($tar->extract()) {
674
my $ret = taint_safe_archive_extract($UPDTmp, $tar);
551
678
warn "error: couldn't extract the tar archive!\n";
552
dbg("channel: archive extraction failed, channel failed");
679
channel_failed("channel: archive extraction failed");
686
if (!lint_check_dir($UPDTmp)) {
687
warn "error: lint check of update failed! channel failed\n";
688
channel_failed("channel: lint check of update failed");
693
# OK, lint passed. now create the update config file
559
695
dbg("channel: creating update config file");
560
unless (open(CF, ">$UPDDir.cf")) {
561
die "fatal: can't create new channel cf $UPDDir.cf: $!\n";
696
unless (open(CF, ">$CFFTmp")) {
697
die "fatal: can't create new channel cf $CFFTmp: $!\n";
564
700
# Put in whatever metadata we need
565
701
print CF "# UPDATE version $newV\n";
703
# try to figure out the relative path dir name
704
my $relativeDir = $UPDDir;
705
$UPDDir =~ m,/([^/]+)/*$,;
709
dbg("channel: updatedir=$UPDDir relativepath=$relativeDir");
567
712
# now include *.cf
568
unless (opendir(DIR, $UPDDir)) {
569
die "fatal: can't access $UPDDir: $!\n";
713
unless (opendir(DIR, $UPDTmp)) {
714
die "fatal: can't access $UPDTmp: $!\n";
571
716
while(my $file = readdir(DIR)) {
572
next unless (-f "$UPDDir/$file");
573
next unless ($file =~ /\.cf$/);
717
$file =~ /^([^\/]+)$/; # untaint
719
next unless (-f "$UPDTmp/$file");
720
next if ($file eq "MIRRORED.BY"); # handled separately
574
722
dbg("channel: adding $file");
575
print CF "include $UPDDir/$file\n";
724
if ($file =~ /\.cf$/) {
725
print CF "include $relativeDir/$file\n";
728
push (@files, $file);
732
warn "write to $CFFTmp failed! attempting to continue";
733
channel_failed("write to $CFFTmp failed");
737
dbg("channel: applying changes to $UPDDir...");
739
# too late to stop now! At this stage, if there are errors,
740
# we have to attempt to carry on regardless, since we've already
741
# blown away the old ruleset.
743
# clean out the "real" update dir, and copy from tmp areas
744
if (!clean_update_dir($UPDDir)) {
745
warn("channel: attempt to rm contents failed, attempting to continue anyway");
748
foreach my $file (@files) {
749
rename("$UPDTmp/$file", "$UPDDir/$file")
750
or warn "rename $UPDTmp/$file $UPDDir/$file failed: $!";
753
unlink $CFFile || warn "error: can't remove file $CFFile: $!\n";
754
cross_fs_rename($CFFTmp, $CFFile)
755
or warn "rename $CFFTmp $CFFile failed: $!";
757
unlink("$UPDDir/MIRRORED.BY");
758
rename("$UPDTmp/MIRRORED.BY", "$UPDDir/MIRRORED.BY")
759
or warn "error: couldn't mv $UPDTmp/MIRRORED.BY $UPDDir/MIRRORED.BY: $!\n";
762
$exit = 0; # "exit 0" means an update occurred
580
764
dbg("channel: update complete");
583
unlink $content_file || warn "error: couldn't remove tmpfile $content_file: $!\n";
767
# clear out the temp files if they still exist
768
foreach ( $newcf_file, $content_file ) {
770
unlink $_ || warn "error: can't remove file $_: $!\n";
585
774
dbg("diag: updates complete, exiting with code $exit");
779
warn("$reason, channel failed\n");
783
$exit = 4; # 4 or higher means channel failed
787
sub taint_safe_archive_extract {
791
# stupid Archive::Tar is not natively taint-safe! duh.
792
# return $tar->extract();
793
# instead, get the file list, untaint, and extract one-by-one.
795
my @files = $tar->list_files();
796
foreach my $file (@files) {
797
next if ($file =~ /^\/$/); # ignore dirs
799
$file =~ /^([-\.\,\/a-zA-Z0-9_]+)$/;
801
$outfname =~ s/\.\.\//__\//gs; # avoid "../" dir traversal attacks
802
$outfname = "$todir/$outfname";
804
dbg "extracting: $outfname";
805
if (open OUT, ">".$outfname) {
806
print OUT $tar->get_content($file);
808
warn "error: write failed to $outfname: $!";
812
warn "error: failed to open $outfname for write: $!";
820
return; # undef = failure
588
823
# Do a generic TXT query
589
824
sub do_txt_query {
590
825
my($query) = shift;
617
852
my($url, $ims) = @_;
619
my $request = HTTP::Request->new("GET");
623
my $str = time2str($ims);
624
$request->header('If-Modified-Since', $str);
625
dbg("http: IMS GET request, $url, $str");
628
dbg("http: GET request, $url");
631
my $response = $ua->request($request);
633
if ($response->is_success) {
634
return $response->content;
637
dbg("http: request failed: " . $response->status_line);
857
# retry 3 times; this works better with Coral
858
foreach my $retries (1 .. 3) {
859
my $request = HTTP::Request->new("GET");
863
my $str = time2str($ims);
864
$request->header('If-Modified-Since', $str);
865
dbg("http: IMS GET request, $url, $str");
868
dbg("http: GET request, $url");
871
$response = $ua->request($request);
873
if ($response->is_success) {
874
return $response->content;
877
# could be a "304 not modified" or similar.
878
# TODO: should use a special return type for "not modified" here
879
# instead of overloading the failure return type
880
if ($ims && $response->status_line =~ /^3/) {
884
# include the text in the debug output; it's useful in some cases,
885
# e.g. proxies that require authentication, diagnosing fascist
886
# filtering false positives, etc.
887
$text = $response->content;
888
$text ||= "(no body)";
891
dbg ("http: request failed, retrying: " . $response->status_line.": ".$text);
894
# this should be a user-readable warning without --debug
895
warn "http: request failed: " . $response->status_line.": ".$text."\n";
701
959
pod2usage( -verbose => $verbose, -message => $message, -exitval => 64 );
963
dbg("gpg: Searching for 'gpg' in ".$ENV{'PATH'});
965
foreach my $dir (split(/:/, $ENV{'PATH'})) {
966
$dir = File::Spec->catfile($dir, 'gpg');
972
die "fatal: couldn't find GPG in \$PATH\n" unless ($path);
973
dbg("gpg: found $path");
977
sub interpolate_gpghomedir {
979
if ($opt{'gpghomedir'}) {
980
$gpghome = $opt{'gpghomedir'};
981
$gpghome =~ s/\'/\\\'/gs;
982
$gpghome = "--homedir='$gpghome'";
990
$GPGPath = find_gpg_path();
991
my $gpghome = interpolate_gpghomedir();
993
my $CMD = "$GPGPath $gpghome --batch ".
994
"--no-tty --status-fd=1 -q --logger-fd=1 --import";
996
unless (open(CMD, "$CMD $keyfile|")) {
997
die "fatal: couldn't execute $GPGPath: $!\n";
1000
# Determine the fate of the signature
1001
while(my $GNUPG = <CMD>) {
1003
dbg ("gpg: $GNUPG");
1005
if ($GNUPG =~ /^gpg: /) {
1006
warn $GNUPG."\n"; # report bad news
1009
if ($GNUPG =~ /^IMPORTED /) {
1010
print "sa-update --import: success. $GNUPG\n";
1018
sub import_default_keyring {
1019
my $defkey = File::Spec->catfile ($DEF_RULES_DIR, "sa-update-pubkey.txt");
1020
return unless (-f $defkey);
1022
print "sa-update: importing default keyring to '".$opt{gpghomedir}."'...\n";
1023
unless (-d $opt{gpghomedir}) {
1024
# use 0700 to avoid "unsafe permissions" warning
1025
mkdir ($opt{gpghomedir}, 0700) or die "cannot mkdir $opt{gpghomedir}: $!";
1027
import_gpg_key($defkey);
1030
sub is_valid_gpg_key_id {
1031
# either a keyid (8 bytes) or a fingerprint (40 bytes)
1032
return ($_[0] =~ /^[a-fA-F0-9]+$/ && (length $_[0] == 8 || length $_[0] == 40));
1035
sub clean_update_dir {
1037
unless (opendir(DIR, $dir)) {
1038
warn "error: can't readdir $dir: $!\n";
1039
dbg("channel: attempt to readdir failed, channel failed");
1042
while(my $file = readdir(DIR)) {
1043
next unless (-f "$dir/$file");
1044
next if ($file eq 'MIRRORED.BY');
1045
dbg("channel: unlinking $file");
1046
$file =~ /^([^\/]+)$/; # untaint
1048
if (!unlink "$dir/$file") {
1049
warn "error: can't remove file $dir/$file: $!\n";
1057
sub lint_check_dir {
1060
# due to the Logger module's globalness (all M::SA objects share the same
1061
# Logger setup), we can't change the debug level here to only include
1062
# "config" or otherwise be more terse. :(
1063
my $spamtest = new Mail::SpamAssassin( {
1064
rules_filename => $dir,
1065
site_rules_filename => "$dir/doesnotexist",
1066
userprefs_filename => "$dir/doesnotexist",
1068
local_tests_only => 1,
1069
dont_copy_prefs => 1,
1072
DEF_RULES_DIR => $DEF_RULES_DIR,
1073
LOCAL_RULES_DIR => $LOCAL_RULES_DIR,
1074
LOCAL_STATE_DIR => $LOCAL_STATE_DIR,
1077
# need to kluge disabling bayes since it may try to expire the DB, and
1078
# without the proper config it's not going to be good.
1079
$spamtest->{conf}->{use_bayes} = 0;
1081
my $res = $spamtest->lint_rules();
1082
$spamtest->finish();
1087
# a version of rename() that can cope with renaming files across filesystems,
1089
sub cross_fs_rename {
1090
my ($from, $to) = @_;
1091
my $ret = rename ($from, $to);
1094
return $ret; # success first time! great
1098
if (!copy($from, $to)) {
1099
# copy failed, too. we have no further fallbacks; return the rename()
1104
# copy succeeded, we're good; remove the source, and return success
704
1109
# ---------------------------------------------------------------------------
736
1142
=head1 DESCRIPTION
738
sa-update automates the process of downloading and installing new
739
rules and configuration, based on channels. The default channel
740
is I<updates.spamassassin.org>, which has updated rules since the
1144
sa-update automates the process of downloading and installing new rules and
1145
configuration, based on channels. The default channel is
1146
I<updates.spamassassin.org>, which has updated rules since the previous
743
Update archives are verified by SHA1 hashes, and optionally GPG.
1149
Update archives are verified by default using SHA1 hashes and GPG signatures.
751
Typically sa-update will use whatever the default site rules directory
752
SpamAssassin uses. (usually /etc/mail/spamassassin) If the updates should be
753
stored in another location, specify it here.
1155
=item B<--updatedir>
1157
By default, C<sa-update> will use the system-wide rules update directory:
1159
@@LOCAL_STATE_DIR@@/spamassassin/@@VERSION@@
1161
If the updates should be stored in another location, specify it here.
757
1165
sa-update can update multiple channels at the same time. By default, it will
758
1166
only access "updates.spamassassin.org", but more channels can be specified via
762
1170
sa-update --channel foo.example.com --channel bar.example.com
1172
=item B<--channelfile>
766
Similar to the B<channel> option, except specify the additional channels in a
1174
Similar to the B<--channel> option, except specify the additional channels in a
767
1175
file instead of on the commandline. This is extremely useful when there are a
768
1176
lot of additional channels.
772
sa-update only verifies update archives by use of a SHA1 checksum. While this
773
verifies whether or not the downloaded archive has been corrupted, it does not
774
offer any form of security regarding whether or not the downloaded archive is
775
legitimate (aka: non-modifed by evildoers). Use this option to enable GPG
776
verification of the archive to solve the problem.
778
Note: Use of the following gpgkey-related options will automatically enable
1178
=item B<--gpg>, B<--nogpg>
1180
sa-update by default will verify update archives by use of a SHA1 checksum
1181
and GPG signature. SHA1 hashes can verify whether or not the downloaded
1182
archive has been corrupted, but it does not offer any form of security
1183
regarding whether or not the downloaded archive is legitimate (aka:
1184
non-modifed by evildoers). GPG verification of the archive is used to
1187
If you wish to skip GPG verification, you can use the B<--nogpg> option
1188
to disable its use. Use of the following gpgkey-related options will
1189
override B<--nogpg> and keep GPG verification enabled.
781
1191
Note: Currently, only GPG itself is supported (ie: not PGP). v1.2 has been
782
1192
tested, although later versions ought to work as well.
786
1196
sa-update has the concept of "release trusted" GPG keys. When an archive is
787
1197
downloaded and the signature verified, sa-update requires that the signature