1#!/usr/bin/perl -w -T
2
3# <@LICENSE>
4# Licensed to the Apache Software Foundation (ASF) under one or more
5# contributor license agreements.  See the NOTICE file distributed with
6# this work for additional information regarding copyright ownership.
7# The ASF licenses this file to you under the Apache License, Version 2.0
8# (the "License"); you may not use this file except in compliance with
9# the License.  You may obtain a copy of the License at:
10#
11#     http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS,
15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16# See the License for the specific language governing permissions and
17# limitations under the License.
18# </@LICENSE>
19
20use strict;
21use warnings;
22use re 'taint';
23
24my $PREFIX          = '@@PREFIX@@';             # substituted at 'make' time
25my $DEF_RULES_DIR   = '@@DEF_RULES_DIR@@';      # substituted at 'make' time
26my $LOCAL_RULES_DIR = '@@LOCAL_RULES_DIR@@';    # substituted at 'make' time
27my $LOCAL_STATE_DIR = '@@LOCAL_STATE_DIR@@';    # substituted at 'make' time
28my $RE2C_BIN        = '@@RE2C_BIN@@';           # substituted at 'make' time
29use lib '@@INSTALLSITELIB@@';                   # substituted at 'make' time
30
31use Errno qw(EBADF);
32use File::Spec;
33use Config;
34use POSIX qw(locale_h setsid sigprocmask _exit);
35
36POSIX::setlocale(LC_TIME,'C');
37
38BEGIN {                          # see comments in "spamassassin.raw" for doco
39  my @bin = File::Spec->splitpath($0);
40  my $bin = ($bin[0] ? File::Spec->catpath(@bin[0..1], '') : $bin[1])
41            || File::Spec->curdir;
42
43  if (-e $bin.'/lib/Mail/SpamAssassin.pm'
44        || !-e '@@INSTALLSITELIB@@/Mail/SpamAssassin.pm' )
45  {
46    my $searchrelative;
47    $searchrelative = 1;    # disabled during "make install": REMOVEFORINST
48    if ($searchrelative && $bin eq '../' && -e '../blib/lib/Mail/SpamAssassin.pm')
49    {
50      unshift ( @INC, '../blib/lib' );
51    } else {
52      foreach ( qw(lib ../lib/site_perl
53                ../lib/spamassassin ../share/spamassassin/lib))
54      {
55        my $dir = File::Spec->catdir( $bin, split ( '/', $_ ) );
56        if ( -f File::Spec->catfile( $dir, "Mail", "SpamAssassin.pm" ) )
57        { unshift ( @INC, $dir ); last; }
58      }
59    }
60  }
61}
62
63use Mail::SpamAssassin;
64use Mail::SpamAssassin::Util qw(untaint_var exit_status_str);
65use Mail::SpamAssassin::Logger;
66use Getopt::Long;
67use File::Copy;
68use File::Path;
69use Pod::Usage;
70use Data::Dumper;
71
72our %opt;
73Mail::SpamAssassin::Util::clean_path_in_taint_mode();
74untaint_var( \%ENV );
75
76Getopt::Long::Configure(
77  qw(bundling no_getopt_compat
78    permute no_auto_abbrev no_ignore_case)
79);
80
81GetOptions(
82  'list'		=> \$opt{'list'},
83  'sudo'		=> \$opt{'sudo'},
84  'quiet'               => \$opt{'quiet'},
85  'keep-tmps'		=> \$opt{'keep-tmps'},
86
87  'configpath|config-file|config-dir|c|C=s' => \$opt{'configpath'},
88  'prefspath|prefs-file|p=s'                => \$opt{'prefspath'},
89  'siteconfigpath=s'                        => \$opt{'siteconfigpath'},
90  'updatedir=s'                             => \$opt{'updatedir'},
91  'cf=s'                                    => \@{$opt{'cf'}},
92  'debug|D:s'       => \$opt{'debug'},
93  'help|h|?'        => \$opt{'help'},
94  'version|V'       => \$opt{'version'},
95  )
96  or usage( 0, "Unknown option!" );
97
98if ( defined $opt{'help'} ) {
99  usage( 0, "For more information read the manual page" );
100}
101if ( defined $opt{'version'} ) {
102  print "SpamAssassin version " . Mail::SpamAssassin::Version() . "\n"
103    or die "error writing: $!";
104  exit 0;
105}
106
107# Check for some dependencies and provide useful error messages if they aren't
108# present
109eval("use ExtUtils::MakeMaker");
110if ($@) {
111  print "$0 requires ExtUtils::MakeMaker for proper operation.\n"
112    or die "error writing: $!";
113  exit 1;
114}
115unless (qx($RE2C_BIN -V)) {
116  print "$0 requires $RE2C_BIN for proper operation.\n"
117    or die "error writing: $!";
118  exit 1;
119}
120
121sub usage {
122  my ( $exitval, $message ) = @_;
123  $exitval ||= 64;
124
125  if ($exitval == 0) {
126    print_version();
127    print("\n")  or die "error writing: $!";
128  }
129  pod2usage(
130    -verbose => 0,
131    -message => $message,
132    -exitval => $exitval,
133  );
134}
135
136# set debug areas, if any specified (only useful for command-line tools)
137if (defined $opt{'debug'}) {
138  $opt{'debug'} ||= 'all';
139}
140
141# at least info
142$opt{'debug'} ||= 'info';
143
144my $quiet = $opt{'quiet'} || 0;
145
146# ensure the body-rule base extractor plugin is loaded, we use that
147my $post_config = q(
148  loadplugin Mail::SpamAssassin::Plugin::BodyRuleBaseExtractor
149
150).join("\n", @{$opt{'cf'}})."\n";
151
152my $spamtest = new Mail::SpamAssassin(
153  {
154    rules_filename      => $opt{'configpath'},
155    site_rules_filename => $opt{'siteconfigpath'},
156    userprefs_filename  => $opt{'prefspath'},
157    debug               => $opt{'debug'},
158    local_tests_only    => 1,
159    dont_copy_prefs     => 1,
160    PREFIX              => $PREFIX,
161    DEF_RULES_DIR       => $DEF_RULES_DIR,
162    LOCAL_RULES_DIR     => $LOCAL_RULES_DIR,
163    LOCAL_STATE_DIR     => $LOCAL_STATE_DIR,
164    post_config_text    => $post_config,
165  }
166);
167
168# appropriate BodyRuleBaseExtractor settings for rule2xs usage
169$spamtest->{base_extract} = 1;
170$spamtest->{bases_must_be_casei} = 1;
171$spamtest->{bases_can_use_alternations} = 0;
172$spamtest->{bases_can_use_quantifiers} = 0;
173$spamtest->{bases_can_use_char_classes} = 0;
174$spamtest->{bases_split_out_alternations} = 1;
175$spamtest->{base_quiet} = $quiet;
176
177if (defined $opt{'updatedir'}) {
178  $opt{'updatedir'} = Mail::SpamAssassin::Util::untaint_file_path($opt{'updatedir'});
179}
180else {
181  $opt{'updatedir'} = $spamtest->sed_path('__local_state_dir__/compiled/__perl_major_ver__/__version__');
182}
183my $installdir = $opt{'updatedir'};
184if ((!defined $opt{'list'})
185        && !$opt{'sudo'}
186        && -d $installdir && !-w $installdir)
187{
188  die "sa-compile: cannot write to $installdir, aborting\n";
189}
190
191$spamtest->{bases_cache_dir} = $spamtest->get_and_create_userstate_dir()
192                        ."/sa-compile.cache";
193
194$spamtest->init(1);
195my $conf = $spamtest->{conf};
196
197# this actually extracts the base rules in the plugin, as a side-effect
198my $res = $spamtest->lint_rules();
199if ($res) {
200  die "sa-compile: not compiling; 'spamassassin --lint' check failed!\n";
201}
202
203if ( defined $opt{'list'} ) {
204  foreach my $ruletype (sort keys %{$conf->{base_orig}}) {
205    print dump_base_strings($ruletype);
206  }
207}
208else {
209  compile_base_strings();
210}
211
212$spamtest->finish();
213
214# make sure we notice any write errors while flushing output buffer
215close STDOUT  or die "error closing STDOUT: $!";
216close STDIN   or die "error closing STDIN: $!";
217
218exit;
219
220##############################################################################
221
222sub dump_base_strings {
223  my ($ruletype) = @_;
224
225  my $s = "name $ruletype\n";
226
227  foreach my $key1 (sort keys %{$conf->{base_orig}->{$ruletype}}) {
228    $s .= "orig $key1 $conf->{base_orig}->{$ruletype}->{$key1}\n";
229  }
230
231  foreach my $key (sort keys %{$conf->{base_string}->{$ruletype}}) {
232    $s .= "r $key:$conf->{base_string}->{$ruletype}->{$key}\n";
233  }
234  return $s;
235}
236
237##############################################################################
238
239sub dump_as_perl {
240  my ($ruletype) = @_;
241
242  my %todump = (
243    name => $ruletype,
244    base_orig => $conf->{base_orig}->{$ruletype},
245    base_string => $conf->{base_string}->{$ruletype}
246  );
247  my $s = Data::Dumper->Dump([ \%todump ], [qw(bases)]);
248  return $s;
249}
250
251##############################################################################
252
253sub compile_base_strings {
254  my $dirpath = Mail::SpamAssassin::Util::secure_tmpdir();
255  die "secure_tmpdir failed" unless $dirpath && -w $dirpath;
256
257  my $sudo = ($opt{sudo} ? 'sudo ' : '');
258
259  foreach my $ruletype (sort keys %{$conf->{base_orig}})
260  {
261    # create the bases.in file
262
263    my $basespath = "bases_$ruletype.in";
264    $basespath =~ s/[^A-Za-z0-9_\.]/_/gs;
265    open OUT, ">$dirpath/$basespath"
266      or die "cannot create $dirpath/$basespath: $!";
267    print OUT dump_base_strings($ruletype)
268      or die "error writing to $dirpath/$basespath: $!";
269    close OUT
270      or die "error closing $dirpath/$basespath: $!";
271
272    # compile it...
273
274    chdir $dirpath  or die "cannot chdir to $dirpath: $!";
275    if (!$quiet) { print "cd $dirpath\n"  or die "error writing: $!" }
276
277    rule2xs($basespath);
278
279    my $log = "";
280    if ($quiet) {
281      $log = ">>$dirpath/log";
282      # empty it
283      open(ZERO, ">$dirpath/log")  or die "cannot create $dirpath/log: $!";
284      close ZERO  or die "error closing $dirpath/log: $!";
285    }
286
287    run(get_perl()." Makefile.PL ".
288          "PREFIX=$dirpath/ignored INSTALLSITEARCH=$installdir $log");
289
290    run($Config{make}." PREFIX=$dirpath/ignored INSTALLSITEARCH=$installdir $log"); #change to $Config{make}. bug 7294
291    run($sudo.$Config{make}." install PREFIX=$dirpath/ignored INSTALLSITEARCH=$installdir $log");	# into $installdir
292
293    # and generate the bases.pl file, for perl consumers
294
295    my $plpath = "bases_$ruletype.pl";
296    $plpath =~ s/[^A-Za-z0-9_\.]/_/gs;
297    open(OUT, ">$dirpath/$plpath")
298      or die "cannot create $dirpath/$plpath: $!";
299    print OUT dump_as_perl($ruletype)
300      or die "error writing to $dirpath/$plpath: $!";
301    close OUT
302      or die "error closing $dirpath/$plpath: $!";
303
304    run($sudo."cp $dirpath/$plpath $installdir/$plpath");
305  }
306
307  if (!$opt{'keep-tmps'}) {
308    chdir '/';
309    if (!$quiet) {
310      # saves trouble on MacOS, possibly
311      print "cd /\n"  or die "error writing: $!";
312    }
313    run($sudo."rm -rf $dirpath");       # cleanup
314  }
315  else {
316    print "temporary dir left due to --keep-tmps: $dirpath\n"
317      or die "error writing: $!";
318  }
319}
320
321sub run {
322  my @cmd = @_;
323  if (!$quiet) { print join(' ',@cmd)."\n"  or die "error writing: $!" }
324  if (system(@cmd) != 0) {
325    my $msg = $? == -1 ? "failed to execute: $!"
326                       : "failed: ".exit_status_str($?);
327    if (!$quiet) { die "command ".$msg."\n" }
328    else { die "command '".join(' ',@cmd)."' ".$msg."\n" }
329    return 0;
330  }
331  return 1;
332}
333
334sub get_perl {
335  my $perl;
336  if ($^X =~ m|^/|) {
337    $perl = $^X;
338  } else {
339    use Config;
340    $perl = $Config{perlpath};
341    $perl =~ s|/[^/]*$|/$^X|;
342  }
343  return untaint_var($perl);
344}
345
346##############################################################################
347
348use constant MAX_RULES_PER_C_FILE => 200;
349
350sub rule2xs {
351  my $modname;
352  my $force = 1;
353  my $FILE = shift;
354
355  if (!$quiet) { print "reading $FILE\n" or die "error writing: $!" }
356  open(my $fh, $FILE)  or die "cannot open $FILE: $!";
357# read ruleset name from the first line in the file
358  my $ruleset_name;
359  $_ = <$fh>;
360  defined $_  or die "error reading $FILE: $!";
361  local ($1);
362  if (/^name\s+(\S+)/) {
363    $ruleset_name = untaint_var($1);
364  }
365
366  if (!$modname) {
367    $modname = "Mail::SpamAssassin::CompiledRegexps::$ruleset_name";
368  }
369
370  our $PATH = $modname;
371  $PATH =~ s/::/-/g;
372  $PATH =~ s/[^-_A-Za-z0-9\.]/_/g;
373  our $PMFILE = $modname;
374  $PMFILE =~ s/.*:://;
375  $PMFILE .= ".pm";
376  our $XSFILE = $PMFILE;
377  $XSFILE =~ s/\.pm$/.xs/;
378
379  $force and system("rm -rf $PATH");
380  mkdir $PATH or (!$force and die "mkdir($PATH): $!");
381  chdir $PATH;
382  if (!$quiet) { print "cd $PATH\n" or die "error writing: $!" }
383
384  my $cprefix = $modname; $cprefix =~ s/[^A-ZA-z0-9]+/_/gs;
385
386  my $numscans = 0;
387  my $has_rules = '';
388
389  while (!eof($fh)) {
390    $numscans++;
391
392    open(my $re, ">scanner${numscans}.re")
393      or die "cannot create scanner${numscans}.re: $!";
394
395    print $re <<EOT  or die "error writing: $!";
396#define NULL            ((char*) 0)
397#define YYCTYPE         unsigned char
398#define YYCURSOR        *p
399#define YYLIMIT         *p
400#define YYMARKER        q
401#define YYFILL(n)
402
403/* backtrack to return other, semi-overlapped tokens; e.g.
404   allow "abcdef" to return both "abc" and "cde" as tokens */
405#define RET(x)          { YYCURSOR = YYMARKER; return (x); }
406EOT
407
408    print $re <<EOT  or die "error writing: $!";
409char *${cprefix}_scan${numscans}(unsigned char **p){
410unsigned char *q = 1 + *p;
411/*!re2c
412EOT
413
414    my $line = 0;
415    my $rulecount = 0;
416    for ($!=0; <$fh>; $!=0) {
417      next if /^#/;
418
419      local ($1,$2);
420      if (/^orig\s+(\S+)\s+(.*)$/) {
421	my $name = $1;
422	my $regexp = $2;
423	$name =~ s/#/[hash]/gs;
424	$regexp =~ s/#/[hash]/gs;
425	$has_rules .= "  q#$name# => q#$regexp#,\n";
426        $rulecount++;
427	next;
428      }
429
430      my ($regexp, $reason) = /^r (.*):(.*)$/;
431      die "no 'r REGEXP:REASON' in $_" unless defined $regexp;
432
433      eval {
434	print $re "\t",
435          Mail::SpamAssassin::Plugin::BodyRuleBaseExtractor::fixup_re($regexp),
436          "            {RET(\"$reason\");}\n"
437          or die "error writing: $!";
438	$line++; 1;
439      } or do {
440        my $eval_stat = $@ ne '' ? $@ : "errno=$!";  chomp $eval_stat;
441        handle_fixup_error($eval_stat, $regexp, $reason);
442      };
443      last if $line == MAX_RULES_PER_C_FILE;
444    }
445    defined $_ || $!==0  or
446      $!==EBADF ? dbg("error reading from $FILE: $!")
447                : die "error reading from $FILE: $!";
448
449    print $re <<EOT  or die "error writing: $!";
450  [\\000-\\377]        { return NULL; }
451*/
452}
453EOT
454
455  }
456
457  for (1..$numscans) {
458    my $cmd = "$RE2C_BIN -i -b -o scanner$_.c scanner$_.re";
459    if (!run($cmd)) {
460      # this must be fatal; it can result in corrupt output modules missing
461      # scannerN() functions
462      my $cwd = `pwd`; chop $cwd;
463      die "'$cmd' failed, dying!\n".
464          "Have you got a sufficiently-recent version of $RE2C_BIN?\n".
465	  "see $cwd/scanner$_.re\n";
466    }
467  }
468
469  my $ccopt = $Config{optimize};      # typically "-O2"
470
471  open(FILE, ">Makefile.PL")  or die "cannot create Makefile.PL: $!";
472  print FILE <<"EOT"  or die "error writing to Makefile.PL: $!";
473    use ExtUtils::MakeMaker;
474
475    WriteMakefile(
476	'NAME' => '$modname',
477	'VERSION_FROM' => '$PMFILE',
478	'ABSTRACT_FROM' => '$PMFILE',
479	'OBJECT' => '\$(O_FILES)',
480	'OPTIMIZE' => '$ccopt',
481	'AUTHOR' => 'A. U. Tomated <automated\@example.com>',
482    );
483EOT
484  close FILE  or die "error closing Makefile.PL: $!";
485
486  open(FILE, ">MANIFEST.SKIP")  or die "cannot create MANIFEST.SKIP: $!";
487  print FILE <<'EOT'  or die "error writing to MANIFEST.SKIP: $!";
488CVS/.*
489\.bak$
490\.sw[a-z]$
491\.tar$
492\.tgz$
493\.tar\.gz$
494\.o$
495\.xsi$
496\.bs$
497^.#
498^tmp/
499^blib/
500^Makefile$
501^Makefile\.[a-z]+$
502^pm_to_blib$
503~$
504EOT
505  close FILE  or die "error closing MANIFEST.SKIP: $!";
506
507  open(my $re, ">$XSFILE")  or die "cannot create $XSFILE: $!";
508  print $re <<"EOT"  or die "error writing to $XSFILE: $!";
509#include "EXTERN.h"
510#include "perl.h"
511#include "XSUB.h"
512
513  /* bug 5556: newSVpvn_share() is not a defined API in perl 5.6.x.
514   * Thankfully we can use newSVpvn() without much harm, losing only
515   * a tiny bit of performance (I'd reckon ;).
516   */
517#ifdef newSVpvn_share
518#define my_newSVpvn_share(x,y,z)    newSVpvn_share(x,y,z)
519#else
520#define my_newSVpvn_share(x,y,z)    newSVpvn(x,y)
521#endif
522
523  /* split single-space-separated result string */
524  static void
525  split_and_add (AV *results, char *match)
526  {
527      char *wordstart, *cp;
528
529      for (cp = wordstart = match; *cp != (unsigned char) 0; cp++) {
530	if (*cp == ' ') {
531	  av_push(results,
532	      my_newSVpvn_share(wordstart, cp-wordstart, (U32)0));
533	  wordstart = cp + 1;
534	}
535      }
536      av_push(results,
537	      my_newSVpvn_share(wordstart, cp-wordstart, (U32)0));
538  }
539
540EOT
541
542  # use a buffer string here instead of writing direct to the file,
543  # so we can prepend 'extern' statements (bug 5534)
544  my $xscode = <<"EOT";
545
546MODULE = $modname  PACKAGE = $modname
547
548PROTOTYPES: DISABLE
549
550SV *
551scan(psv)
552	SV* psv
553
554  PREINIT:
555	char *match;
556	unsigned char *cursor;
557	unsigned char *pstart;
558	unsigned char *pend;
559	STRLEN plen;
560	AV *results;
561
562  CODE:
563	pstart = (unsigned char *) SvPVutf8(psv, plen);
564	pend = pstart + plen;
565	results = (AV *) sv_2mortal((SV *) newAV());
566
567EOT
568
569  for (1..$numscans) {
570    my $funcname = $cprefix."_scan".$_;
571
572    $xscode =
573        # prepend this chunk
574        qq{
575
576	  extern char *${funcname} (unsigned char **);
577
578        }.$xscode.
579        # and append this one
580        qq{
581
582	  cursor = pstart;
583	  while (cursor < pend) {
584	    while ((match = ${funcname} (\&cursor)) != NULL) {
585	      split_and_add(results, match);
586	    }
587	  }
588
589        };
590
591  }
592
593  print $re $xscode  or die "error writing: $!";
594  print $re <<EOT  or die "error writing: $!";;
595	  RETVAL = newRV((SV *) results);
596      OUTPUT:
597	  RETVAL
598
599EOT
600
601  close($re)  or die "error closing $XSFILE: $!";
602
603  open(FILE, ">$PMFILE")  or die "cannot create $PMFILE: $!";
604  my $str =<<"EOT";
605
606package $modname;
607
608use strict;
609
610use XSLoader ();
611
612BEGIN {
613our \$VERSION = '1.0';
614our \@ISA = qw(XSLoader);
615our \@EXPORT_OK = qw();
616
617our \$HAS_RULES = {
618  $has_rules
619};
620
621XSLoader::load '$modname', \$VERSION;
622}
623
6241;
625fnord__END__
626
627fnord=head1 NAME
628
629$modname - Efficient string matching for regexps found in $FILE
630
631fnord=head1 SYNOPSIS
632
633  use $modname;
634
635  ...
636  my \$match = ${modname}::scan(\$string);
637
638fnord=head1 DESCRIPTION
639
640This module was created by SpamAssassin with the aid of re2xs, which uses re2c
641to create an XS library capable of scanning through a bunch of regular
642expressions as defined in F<$FILE>.
643
644See C<sa-compile> for more details.
645
646fnord=cut
647EOT
648
649  $str =~ s/^fnord//gm;
650  print FILE $str  or die "error writing to $PMFILE: $!";
651  close FILE  or die "error closing $PMFILE: $!";
652}
653
654sub handle_fixup_error {
655  my ($strat, $regexp, $reason) = @_;
656  if ($strat) {
657    warn "skipped: $regexp: $strat";
658  }
659}
660
661##############################################################################
662
663=head1 NAME
664
665sa-compile - compile SpamAssassin ruleset into native code
666
667=head1 SYNOPSIS
668
669B<sa-compile> [options]
670
671Options:
672
673  --list                        Output base string list to STDOUT
674  --sudo                        Use 'sudo' for privilege escalation
675  --keep-tmps                   Keep temporary files instead of deleting
676  -C path, --configpath=path, --config-file=path
677                                Path to standard configuration dir
678  -p prefs, --prefspath=file, --prefs-file=file
679                                Set user preferences file
680  --siteconfigpath=path         Path for site configs
681                                (default: @@PREFIX@@/etc/mail/spamassassin)
682  --updatedir=path              Directory to place updates
683          (default: @@LOCAL_STATE_DIR@@/compiled/<perlversion>/@@VERSION@@)
684  --cf='config line'            Additional line of configuration
685  -D, --debug [area=n,...]	Print debugging messages
686  -V, --version			Print version
687  -h, --help			Print usage message
688
689=head1 DESCRIPTION
690
691sa-compile uses C<re2c> to compile the site-wide parts of the SpamAssassin
692ruleset. No part of user_prefs or any files included from user_prefs can be
693built into the compiled set.
694
695This compiled set is then used by the
696C<Mail::SpamAssassin::Plugin::Rule2XSBody> plugin to speed up
697SpamAssassin's operation, where possible, and when that plugin is loaded.
698
699C<re2c> can match strings much faster than perl code, by constructing a DFA to
700match many simple strings in parallel, and compiling that to native object
701code.  Not all SpamAssassin rules are amenable to this conversion, however.
702
703This requires C<re2c> (see C<http://re2c.org/>), and the C
704compiler used to build Perl XS modules, be installed.
705
706Note that running this, and creating a compiled ruleset, will have no
707effect on SpamAssassin scanning speeds unless you also edit your C<v320.pre>
708file and ensure this line is uncommented:
709
710  loadplugin Mail::SpamAssassin::Plugin::Rule2XSBody
711
712Additionally, "sa-compile" will not restart "spamd" or otherwise cause a scanner to
713reload the now-compiled ruleset automatically.
714
715=head1 OPTIONS
716
717=over 4
718
719=item B<--list>
720
721Output the extracted base strings to STDOUT, instead of generating
722the C extension code.
723
724=item B<--sudo>
725
726Use C<sudo(8)> to run code as 'root' when writing files to the compiled-rules
727storage area (which is C<@@LOCAL_STATE_DIR@@/compiled/@@PERL_MAJOR_VER@@/@@VERSION@@> by default).
728
729=item B<--quiet>
730
731Produce less diagnostic output.  Errors will still be displayed.
732
733=item B<--keep-tmps>
734
735Keep temporary files after the script completes, instead of
736deleting them.
737
738=item B<-C> I<path>, B<--configpath>=I<path>, B<--config-file>=I<path>
739
740Use the specified path for locating the distributed configuration files.
741Ignore the default directories (usually C</usr/share/spamassassin> or similar).
742
743=item B<--siteconfigpath>=I<path>
744
745Use the specified path for locating site-specific configuration files.  Ignore
746the default directories (usually C</etc/mail/spamassassin> or similar).
747
748=item B<--updatedir>
749
750By default, C<sa-compile> will use the system-wide rules update directory:
751
752        @@LOCAL_STATE_DIR@@/compiled/@@PERL_MAJOR_VER@@/@@VERSION@@
753
754If the updates should be stored in another location, specify it here.
755
756Note that use of this option is not recommended; if sa-compile is placing the
757compiled rules the wrong directory, you probably need to rebuild SpamAssassin
758with different C<Makefile.PL> arguments, instead of overriding sa-compile's
759runtime behaviour.
760
761=item B<--cf='config line'>
762
763Add additional lines of configuration directly from the command-line, parsed
764after the configuration files are read.   Multiple B<--cf> arguments can be
765used, and each will be considered a separate line of configuration.
766
767=item B<-p> I<prefs>, B<--prefspath>=I<prefs>, B<--prefs-file>=I<prefs>
768
769Read user score preferences from I<prefs> (usually
770C<$HOME/.spamassassin/user_prefs>) .
771
772=item B<-D> [I<area,...>], B<--debug> [I<area,...>]
773
774Produce debugging output.  If no areas are listed, all debugging information is
775printed.  Diagnostic output can also be enabled for each area individually;
776I<area> is the area of the code to instrument.
777
778For more information about which areas (also known as channels) are
779available, please see the documentation at
780L<http://wiki.apache.org/spamassassin/DebugChannels>.
781
782=item B<-h>, B<--help>
783
784Print help message and exit.
785
786=item B<-V>, B<--version>
787
788Print sa-compile version and exit.
789
790=back
791
792=head1 SEE ALSO
793
794Mail::SpamAssassin(3)
795spamassassin(1)
796spamd(1)
797
798=head1 PREREQUISITES
799
800C<Mail::SpamAssassin>
801C<re2c>
802C<Mail::SpamAssassin::Plugin::Rule2XSBody>
803
804=head1 BUGS
805
806See <http://issues.apache.org/SpamAssassin/>
807
808=head1 AUTHORS
809
810The Apache SpamAssassin(tm) Project <https://spamassassin.apache.org/>
811
812=head1 LICENSE AND COPYRIGHT
813
814SpamAssassin is distributed under the Apache License, Version 2.0, as
815described in the file C<LICENSE> included with the distribution.
816
817Copyright (C) 2015 The Apache Software Foundation
818
819
820=cut
821
822