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