Mail-SpamAssassin

 view release on metacpan or  search on metacpan

INSTALL  view on Meta::CPAN

    Mail-SpamAssassin-rules-xxx.tgz
    Mail-SpamAssassin-rules-xxx.tgz.asc
    Mail-SpamAssassin-rules-xxx.tgz.sha512
      (where xxx may look something like '4.0.0.r1900144')

Save them all to the current directory.
Obtain a rules-signing public key:

    curl -O https://spamassassin.apache.org/updates/GPG.KEY

Import the signing key to the SpamAssassin gpg keyring, so that the rules
files can be verified safely:

    sa-update --import GPG.KEY

Install rules from a compressed tar archive:

    sa-update --install Mail-SpamAssassin-rules-xxx.tgz

Note that the ".tgz", ".tgz.asc" and ".tgz.sha512" files all need to
be in the same directory, otherwise sa-update will fail.

lib/Mail/SpamAssassin/Util/DependencyInfo.pm  view on Meta::CPAN

   Because perl module LWP does not support IPv6, sa-update as of
   3.4.0 will use these standard programs to download rule updates
   leaving LWP as a fallback if none of the programs are found.

   *IMPORTANT NOTE*: You only need one of these programs 
       It's only a concern if you are warned about all 3 
       i.e. (curl, wget & fetch) missing";

our @OPTIONAL_BINARIES = (
{
  binary => 'gpg',
  version => '0',
  recommended_min_version => '1.0.6',
  version_check_params => '--version',
  version_check_regex => 'gpg \(GnuPG\) ([\d\.]*)',
  desc => 'The "sa-update" program requires this executable to verify  
  encryption signatures.  It is not recommended, but you can use 
  "sa-update" with the --no-gpg to skip the verification. ',
},
{
  binary => 'wget',
  version => '0',
  recommended_min_version => '1.8.2',
  version_check_params => '--version',
  version_check_regex => 'Gnu Wget ([\d\.]*)',
  desc => $lwp_note,
},
{

sa-update.raw  view on Meta::CPAN


##############################################################################

# Default list of GPG keys allowed to sign update releases
#
# pub   4096R/5244EC45 2005-12-20
#       Key fingerprint = 5E54 1DC9 59CB 8BAC 7C78  DFDC 4056 A61A 5244 EC45
# uid                  updates.spamassassin.org Signing Key <release@spamassassin.org>
# sub   4096R/24F434CE 2005-12-20
#
# note for gpg newbs: these are "long" gpg keyids.  It's common to also
# use the last 8 hex digits as a shorter keyid string.
#
my %valid_GPG = ( 
  '0C2B1D7175B852C64B3CDC716C55397824F434CE' => 1,
  '5E541DC959CB8BAC7C78DFDC4056A61A5244EC45' => 1,
);

# Default list of channels to update against
#
my @channels = ( 'updates.spamassassin.org' );

my $IGNORE_MIRBY_OLDER_THAN = (24 * 60 * 60 * 7);       # 1 week

##############################################################################

my %opt;
@{$opt{'gpgkey'}} = ();
@{$opt{'channel'}} = ();
my $GPG_ENABLED = 1;

$opt{'gpghomedir'} = File::Spec->catfile($LOCAL_RULES_DIR, 'sa-update-keys');

Getopt::Long::Configure(
  qw(bundling no_getopt_compat no_auto_abbrev no_ignore_case));
GetOptions(
  'debug|D:s'                           => \$opt{'debug'},
  'version|V'                           => \$opt{'version'},
  'help|h|?'                            => \$opt{'help'},
  'verbose|v+'                          => \$opt{'verbose'},
  'checkonly'                           => \$opt{'checkonly'},
  'allowplugins'                        => \$opt{'allowplugins'},
  'reallyallowplugins'                  => \$opt{'reallyallowplugins'},
  'refreshmirrors'                      => \$opt{'refreshmirrors'},
  'forcemirror=s'                       => \$opt{'forcemirror'},
  'httputil=s'                          => \$opt{'httputil'},
  'score-multiplier=s'                  => \$opt{'score-multiplier'},
  'score-limit=s'                       => \$opt{'score-limit'},

  # allow multiple of these on the commandline
  'gpgkey=s'				=> $opt{'gpgkey'},
  'gpghomedir=s'			=> \$opt{'gpghomedir'},
  'channel=s'				=> $opt{'channel'},

  'install=s'                           => \$opt{'install'},
  'import=s'			        => \$opt{'import'},
  'gpgkeyfile=s'			=> \$opt{'gpgkeyfile'},
  'channelfile=s'			=> \$opt{'channelfile'},
  'updatedir=s'				=> \$opt{'updatedir'},
  'gpg!'				=> \$GPG_ENABLED,

  '4'                                   => sub { $opt{'force_pf'} = 'inet' },
  '6'                                   => sub { $opt{'force_pf'} = 'inet6' },

  # backward compatibility
  'usegpg'				=> \$GPG_ENABLED,

) or print_usage_and_exit();

if ( defined $opt{'help'} ) {               
  print_usage_and_exit("For more information read the sa-update man page.\n", 0);
} 
if ( defined $opt{'version'} ) {            
  print_version();
  exit(0);
}

sa-update.raw  view on Meta::CPAN

});

if (defined $opt{'updatedir'}) {
  $opt{'updatedir'} = untaint_file_path($opt{'updatedir'});
}
else {
  $opt{'updatedir'} = $SA->sed_path('__local_state_dir__/__version__');
}


# check only disabled gpg
# https://bz.apache.org/SpamAssassin/show_bug.cgi?id=5854
if ( defined $opt{'checkonly'}) {
  $GPG_ENABLED=0;
  dbg("gpg: Disabling gpg requirement due to checkonly flag.");
}


dbg("generic: sa-update version $VERSION");
dbg("generic: using update directory: $opt{'updatedir'}");

# doesn't really display useful things for this script, but we do want
# a module/version listing, etc. sa-update may be used for older versions
# of SA that don't include this function, so eval around it.
eval { $SA->debug_diagnostics(); 1; };

sa-update.raw  view on Meta::CPAN


# untaint the command-line args; since the root user supplied these, and
# we're not a setuid script, we trust them
foreach my $optkey (keys %opt) {
  next if ref $opt{$optkey};
  untaint_var(\$opt{$optkey});
}

##############################################################################

# Deal with gpg-related options

if (@{$opt{'gpgkey'}}) {
  $GPG_ENABLED = 1;
  foreach my $key (@{$opt{'gpgkey'}}) {
    unless (is_valid_gpg_key_id($key)) {
      dbg("gpg: invalid gpgkey parameter $key");
      next;
    }
    $key = uc $key;
    dbg("gpg: adding key id $key");
    $valid_GPG{$key} = 1;
  }
}

if (defined $opt{'gpgkeyfile'}) {
  $GPG_ENABLED = 1;
  open(GPG, $opt{'gpgkeyfile'})
    or die "cannot open $opt{'gpgkeyfile'} for reading: $!\n";

  dbg("gpg: reading in gpgfile ".$opt{'gpgkeyfile'});
  while(my $key = <GPG>) {
    chomp $key;

    $key =~ s/#.*$//;   # remove comments
    $key =~ s/^\s+//;   # remove leading whitespace
    $key =~ s/\s+$//;   # remove tailing whitespace
    next if $key eq ''; # skip empty lines

    unless (is_valid_gpg_key_id($key)) {
      dbg("gpg: invalid key id $key");
      next;
    }
    $key = uc $key;
    dbg("gpg: adding key id $key");
    $valid_GPG{$key} = 1;
  }
  close(GPG) or die "cannot close $opt{'gpgkeyfile'}: $!";
}

# At this point, we need to know where GPG is ...
my $GPGPath;
if ($GPG_ENABLED || $opt{'import'}) {
  # find GPG in the PATH
  # bug 4958: for *NIX it's "gpg", in Windows it's "gpg.exe"
  $GPGPath = 'gpg' . $Config{_exe};
  dbg("gpg: Searching for '$GPGPath'");

  if ($GPGPath = Mail::SpamAssassin::Util::find_executable_in_env_path($GPGPath)) {
    dbg("gpg: found $GPGPath");

    # bug 5030: if GPGPath has a space, put it in quotes
    if ($GPGPath =~ / /) {
      $GPGPath =~ s/"/\\"/g;
      $GPGPath = qq/"$GPGPath"/;
      dbg("gpg: path changed to $GPGPath");
    }
  }
  else {
    die "error: gpg required but not found!  It is not recommended, but you can use \"sa-update\" with the --no-gpg to skip the verification. \n";
  }

  # GPG was found, and we've been asked to import a key only
  if ( $opt{'import'} ) {
    my $ex = import_gpg_key($opt{'import'});
    exit $ex;
  }

  # does the sa-update keyring exist?  if not, import it
  if(!-f File::Spec->catfile($opt{'gpghomedir'}, "trustdb.gpg")) {
    import_default_keyring();
    # attempt to continue even if this fails, anyway
  }

  # specify which keys are trusted
  dbg("gpg: release trusted key id list: ".join(" ", keys %valid_GPG));

  # convert fingerprint gpg ids to keyids
  foreach (keys %valid_GPG) {
    my $id = substr $_, -8;
    $valid_GPG{$id} = 1;
  }
}

##############################################################################

# Deal with channel-related options
if (defined $opt{'channel'} && scalar @{$opt{'channel'}} > 0) {

sa-update.raw  view on Meta::CPAN

  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
  # look for : [GNUPG:] GOODSIG 6C55397824F434CE updates.spamassassin.org [...]
  #            [GNUPG:] VALIDSIG 0C2B1D7175B852C64B3CDC716C55397824F434CE [...]
  #            [GNUPG:] NO_PUBKEY 6C55397824F434CE
  if ($GPG) {
    dbg("gpg: populating temp signature file");
    my $sig_file;
    ($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");

    my $gpghome = interpolate_gpghomedir();

    # TODO: we could also use "--keyserver pgp.mit.edu" or similar,
    # to autodownload missing keys...
    my $CMD = "$GPGPath $gpghome --verify --batch ".
        "--no-tty --status-fd=1 -q --logger-fd=1";

    unless (open(CMD, "$CMD $sig_file $content_file|")) {
      unlink $sig_file or warn "error: cannot unlink $sig_file: $!\n";
      die "fatal: couldn't execute $GPGPath: $!\n";
    }

    # Determine the fate of the signature
    my $signer = '';
    my $missingkeys = '';
    while(my $GNUPG = <CMD>) {
      chop $GNUPG;
      dbg ("gpg: $GNUPG");

      if ($GNUPG =~ /^gpg: fatal:/) {
        warn $GNUPG."\n";        # report bad news
      }

      local($1);
      if ($GNUPG =~ /^\Q[GNUPG:]\E NO_PUBKEY \S+(\S{8})$/) {
        $missingkeys .= $1." ";
      }

      next unless ($GNUPG =~ /^\Q[GNUPG:]\E (?:VALID|GOOD)SIG (\S{8,40})/);
      my $key = $1;

sa-update.raw  view on Meta::CPAN

      if (length $key > 8 && length $key < 40) {
        substr($key, 8) = '';
      }

      # use the longest match we can find
      $signer = $key if length $key > length $signer;
    }

    my $errno = 0;  close CMD or $errno = $!;
    proc_status_ok($?,$errno)
      or warn("gpg: process '$GPGPath' finished: ".
              exit_status_str($?,$errno)."\n");

    unlink $sig_file or warn "cannot unlink $sig_file: $!\n";

    if ($signer) {
      my $keyid = substr $signer, -8;
      dbg("gpg: found signature made by key $signer");
      if (exists $valid_GPG{$signer}) {
	dbg("gpg: key id $signer is release trusted");
      }
      elsif (exists $valid_GPG{$keyid}) {
	dbg("gpg: key id $keyid is release trusted");
      }
      else {
	dbg("gpg: key id $keyid is not release trusted");
	$signer = undef;
      }
    }

    unless ($signer) {
      warn "error: GPG validation failed!\n";

      if ($missingkeys) {

        warn <<ENDOFVALIDATIONERR;

sa-update.raw  view on Meta::CPAN

##############################################################################

sub usage {
  my ( $verbose, $message ) = @_;
  print "sa-update version $VERSION\n";
  pod2usage( -verbose => $verbose, -message => $message, -exitval => 64 );
}

##############################################################################

sub interpolate_gpghomedir {
  my $gpghome = '';
  if ($opt{'gpghomedir'}) {
    $gpghome = $opt{'gpghomedir'};
    if (am_running_on_windows()) {
      # windows is single-quote-phobic; bug 4958 cmt 7
      $gpghome =~ s/\"/\\\"/gs;
      $gpghome = "--homedir=\"$gpghome\"";
    } else {
      $gpghome =~ s/\'/\\\'/gs;
      $gpghome = "--homedir='$gpghome'";
    }
  }
  return $gpghome;
}

##############################################################################

sub check_gpghomedir {
  unless (-d $opt{gpghomedir}) {
    dbg("gpg: creating gpg home dir ".$opt{gpghomedir});
    # use 0700 to avoid "unsafe permissions" warning
    mkpath([$opt{gpghomedir}], 0, 0700)
      or die "cannot mkpath $opt{gpghomedir}: $!";
  } 
}

##############################################################################
sub import_gpg_key {
  my $keyfile = shift;

  my $gpghome = interpolate_gpghomedir();
  check_gpghomedir();

  my $CMD = "$GPGPath $gpghome --batch ".
      "--no-tty --status-fd=1 -q --logger-fd=1 --import";

  unless (open(CMD, "$CMD $keyfile|")) {
    die "fatal: couldn't execute $GPGPath: $!\n";
  }

  # Determine the fate of the signature
  while(my $GNUPG = <CMD>) {
    chop $GNUPG;
    dbg ("gpg: $GNUPG");

    if ($GNUPG =~ /^gpg: /) {
      warn $GNUPG."\n";        # report bad news
    }

    if ($GNUPG =~ /^IMPORTED /) {
      dbg("gpg: gpg key imported successfully");
    }
  }

  my $errno = 0;  close CMD or $errno = $!;
  proc_status_ok($?,$errno)
    or warn("gpg: process '$CMD' finished: ".exit_status_str($?,$errno)."\n");

  return ($? >> 8);
}

##############################################################################

sub import_default_keyring {
  my $defkey = File::Spec->catfile ($DEF_RULES_DIR, "sa-update-pubkey.txt");
  unless (-f $defkey) {
    dbg("gpg: import of default keyring failed, couldn't find sa-update-pubkey.txt");
    return;
  }

  dbg("gpg: importing default keyring to ".$opt{gpghomedir});
  check_gpghomedir();
  import_gpg_key($defkey);
}

##############################################################################

sub is_valid_gpg_key_id {
  # either a keyid (8 bytes) or a fingerprint (40 bytes)
  return ($_[0] =~ /^[a-fA-F0-9]+$/ && (length $_[0] == 8 || length $_[0] == 40));
}

##############################################################################

sub clean_update_dir {
  my($dir, $preserve_files_ref) = @_;

  dbg("generic: cleaning directory %s", $dir);

sa-update.raw  view on Meta::CPAN

Options:

  --channel channel       Retrieve updates from this channel
                          Use multiple times for multiple channels
  --channelfile file      Retrieve updates from the channels in the file
  --checkonly             Check for update availability, do not install
  --install file          Install updates directly from this file. Signature
                          verification will use "file.asc", or "file.sha512"
                          or "file.sha256".
  --allowplugins          Allow updates to load plugin code (DANGEROUS)
  --gpgkey key            Trust the key id to sign releases
                          Use multiple times for multiple keys
  --gpgkeyfile file       Trust the key ids in the file to sign releases
  --gpghomedir path       Store the GPG keyring in this directory
  --gpg and --nogpg       Use (or do not use) GPG to verify updates
                          (--gpg is assumed by use of the above
                          --gpgkey and --gpgkeyfile options)
  --import file           Import GPG key(s) from file into sa-update's
                          keyring. Use multiple times for multiple files
  --updatedir path        Directory to place updates, defaults to the
                          SpamAssassin site rules directory
                          (default: @@LOCAL_STATE_DIR@@/@@VERSION@@)
  --refreshmirrors        Force the MIRRORED.BY file to be updated
  --forcemirror url       Use a specific mirror instead of downloading from
                          official mirrors
  --httputil util         Force used download tool. By default first found
                          from these is used: curl, wget, fetch, lwp

sa-update.raw  view on Meta::CPAN

activate plugins; any C<loadplugin> or C<tryplugin> lines will be commented
in the downloaded update rules files.

You should never enable this for 3rd party update channels, since plugins
can execute unrestricted code on your system, even possibly as root! This
includes spamassassin official updates, which have no need to include
running code.

Use --reallyallowplugins option to bypass warnings and make it work.

=item B<--gpg>, B<--nogpg>

sa-update by default will verify update archives by use of GPG signature. 

If you wish to skip GPG verification (very unsafe), you can use the
B<--nogpg> option to disable its use.  Use of the following gpgkey-related
options will override B<--nogpg> and keep GPG verification enabled.

If GPG is disabled, only SHA512 or SHA256 checksums are used to verify
whether or not the downloaded archive has been corrupted, but it does not
offer any form of security regarding whether or not the downloaded archive
is legitimate (aka: non-modifed by evildoers).

Note: Only GnuPG is supported (ie: not any other PGP software).

=item B<--gpgkey>

sa-update has the concept of "release trusted" GPG keys.  When an archive is
downloaded and the signature verified, sa-update requires that the signature
be from one of these "release trusted" keys or else verification fails.  This
prevents third parties from manipulating the files on a mirror, for instance,
and signing with their own key.

By default, sa-update trusts key ids C<24F434CE> and C<5244EC45>, which are
the standard SpamAssassin release key and its sub-key.  Use this option to
trust additional keys.  See the B<--import> option for how to add keys to
sa-update's keyring.  For sa-update to use a key it must be in sa-update's
keyring and trusted.

For multiple keys, use the option multiple times.  i.e.:

	sa-update --gpgkey E580B363 --gpgkey 298BC7D0

Note: use of this option automatically enables GPG verification.

=item B<--gpgkeyfile>

Similar to the B<--gpgkey> option, except specify the additional keys in a file
instead of on the commandline.  This is extremely useful when there are a lot
of additional keys that you wish to trust.

=item B<--gpghomedir>

Specify a directory path to use as a storage area for the C<sa-update> GPG
keyring.  By default, this is

        @@LOCAL_RULES_DIR@@/sa-update-keys

=item B<--import>

Use to import GPG key(s) from a file into the sa-update keyring which is
located in the directory specified by B<--gpghomedir>.  Before using channels
from third party sources, you should use this option to import the GPG key(s)
used by those channels.  You must still use the B<--gpgkey> or B<--gpgkeyfile>
options above to get sa-update to trust imported keys.

To import multiple keys, use the option multiple times.  i.e.:

	sa-update --import channel1-GPG.KEY --import channel2-GPG.KEY

Note: use of this option automatically enables GPG verification.

=item B<--refreshmirrors>

sa-update.raw  view on Meta::CPAN

Note that use of this option is not recommended; if you're just using sa-update
to download updated rulesets for a scanner, and sa-update is placing updates in
the wrong directory, you probably need to rebuild SpamAssassin with different
C<Makefile.PL> arguments, instead of overriding sa-update's runtime behaviour.

=item B<-D> [I<area,...>], B<--debug> [I<area,...>]

Produce debugging output.  If no areas are listed, all debugging information is
printed.  Diagnostic output can also be enabled for each area individually;
I<area> is the area of the code to instrument. For example, to produce
diagnostic output on channel, gpg, and http, use:

        sa-update -D channel,gpg,http

For more information about which areas (also known as channels) are
available, please see the documentation at
L<https://wiki.apache.org/spamassassin/DebugChannels>.

=item B<-h>, B<--help>

Print help message and exit.

=item B<-V>, B<--version>



( run in 1.206 second using v1.01-cache-2.11-cpan-df04353d9ac )