#!/usr/bin/perl -w
my
$opt
= Mail::SpamAssassin::Spamd::Config->new(
{
defaults
=> {
daemonize
=> 1,
port
=> 783, },
moreopts
=> [
qw(httpd_path|httpd-path=s httpd_opt|httpd-opt=s@
httpd_directive|httpd-directive=s@ k:s apxs=s
httpd_conf|httpd-conf=s)
],
}
);
for
my
$option
(
qw(round-robin setuid-with-sql setuid-with-ldap socketpath
socketowner socketgroup socketmode paranoid vpopmail)
)
{
die
"ERROR: --$option can't be used with apache-spamd\n"
if
defined
$opt
->{
$option
};
}
die
"ERROR: '$opt->{httpd_path}' does not exist or not executable\n"
if
exists
$opt
->{httpd_path}
and !-f
$opt
->{httpd_path} || !-x _;
$opt
->{httpd_path} ||=
'httpd'
;
$opt
->{pidfile} ||=
'/var/run/apache-spamd.pid'
if
-w
'/var/run/'
&& -x _ && !-e
'/var/run/apache-spamd.pid'
;
die
"ERROR: --pidfile is mandatory\n"
unless
$opt
->{pidfile};
$opt
->{pidfile} = File::Spec->rel2abs(
$opt
->{pidfile});
if
(-d
$opt
->{pidfile}) {
die
"ERROR: can't write pid, '$opt->{pidfile}' directory not writable\n"
unless
-x _ && -w _;
$opt
->{pidfile} = File::Spec->catfile(
$opt
->{pidfile},
'apache-spamd.pid'
);
}
if
(
exists
$opt
->{k}) {
die
"ERROR: can't use -k with --httpd_conf\n"
if
exists
$opt
->{httpd_conf};
$opt
->{k} ||= -e
$opt
->{pidfile} ?
'stop'
:
'start'
;
die
"ERROR: -k start|stop|restart|reload|graceful|graceful-stop"
.
" or empty for toggle\n"
unless
$opt
->{k} =~ /^(?:start|stop|restart|reload|graceful(?:-stop)?)$/;
}
$opt
->{k} ||=
'start'
;
if
(
exists
$opt
->{httpd_conf}) {
die
"ERROR: --httpd_conf must be a regular file\n"
if
-e
$opt
->{httpd_conf} && !-f _;
$opt
->{httpd_conf} = File::Spec->rel2abs(
$opt
->{httpd_conf})
unless
$opt
->{httpd_conf} eq
'-'
;
}
unless
(
$opt
->{username}) {
warn
"$0: Running as root, huh? Asking for trouble, aren't we?\n"
if
$< == 0;
$opt
->{username} =
getpwuid
($>);
warn
"$0: setting User to '$opt->{username}', pass --username to override\n"
if
$opt
->{debug} =~ /\b(?:all|info|spamd|prefork|config)\b/;
}
my
@directives
;
my
@run
= (
$opt
->{httpd_path},
'-k'
,
$opt
->{k},
'-d'
, Cwd::cwd(),
);
if
(
$opt
->{debug} =~ /\ball\b/) {
push
@run
,
qw(-e debug)
;
push
@directives
,
'LogLevel debug'
;
}
push
@run
,
'-X'
if
!
$opt
->{daemonize};
push
@run
, @{
$opt
->{httpd_opts} }
if
exists
$opt
->{httpd_opts};
push
@directives
,
'ServerName '
. hostname(),
qq(PidFile "$opt->{pidfile}")
,
qq(ErrorLog "$opt->{'log-file'}")
;
if
(
$opt
->{k} !~ /stop|graceful/) {
my
$modlist
=
join
' '
, static_apache_modules(
$opt
->{httpd_path});
push
@directives
,
'LoadModule perl_module '
. apache_module_path(
'mod_perl.so'
)
if
$modlist
!~ /\bmod.perl\.c\b/i;
my
$mpm
=
lc
(
(
$modlist
=~ /\b(prefork|worker|mpm_winnt|mpmt_os2
|mpm_netware|beos|event|metuxmpm|peruser)\.c\b/ix
)[0]
);
die
"ERROR: unable to figure out which MPM is in use\n"
unless
$mpm
;
push
@directives
, mpm_specific_config(
$mpm
);
push
@directives
, @{
$opt
->{httpd_directive} }
if
exists
$opt
->{httpd_directive};
push
@directives
,
"TimeOut $opt->{'timeout-tcp'}"
if
$opt
->{
'timeout-tcp'
};
push
@directives
,
defined
$opt
->{
'listen-ip'
}
&& @{
$opt
->{
'listen-ip'
} }
?
map
({
'Listen '
. (
$_
=~ /:/ ?
"[$_]"
:
$_
) .
":$opt->{port}"
}
@{
$opt
->{
'listen-ip'
} })
:
"Listen $opt->{port}"
;
if
(
$opt
->{ssl}) {
push
@directives
,
'LoadModule ssl_module '
. apache_module_path(
'mod_ssl.so'
)
if
$modlist
!~ /\bmod.ssl\.c\b/i;
push
@directives
,
qq(SSLCertificateFile "$opt->{'server-cert'}")
if
exists
$opt
->{
'server-cert'
};
push
@directives
,
qq(SSLCertificateKeyFile "$opt->{'server-key'}")
if
exists
$opt
->{
'server-key'
};
push
@directives
,
'SSLEngine on'
;
my
$random
= -r
'/dev/urandom'
?
'file:/dev/urandom 256'
:
'builtin'
;
push
@directives
,
"SSLRandomSeed startup $random"
,
"SSLRandomSeed connect $random"
;
}
push
@directives
,
'LoadModule ident_module '
. apache_module_path(
'mod_ident.so'
),
'IdentityCheck on'
if
$opt
->{
'auth-ident'
};
push
@directives
,
"IdentityCheckTimeout $opt->{'ident-timeout'}"
if
$opt
->{
'auth-ident'
} &&
defined
$opt
->{
'ident-timeout'
};
push
@directives
,
'PerlLoadModule Mail::SpamAssassin::Spamd::Apache2::Config'
,
'SAenabled on'
;
push
@directives
,
"SAAllow from @{$opt->{'allowed-ips'}}"
if
exists
$opt
->{
'allowed-ips'
};
push
@directives
,
'SAtell on'
if
$opt
->{
'allow-tell'
};
push
@directives
,
"SAtimeout $opt->{'timeout-child'}"
if
exists
$opt
->{
'timeout-child'
};
push
@directives
,
"SAdebug $opt->{debug}"
if
$opt
->{debug};
push
@directives
,
'SAident on'
if
$opt
->{
'auth-ident'
};
push
@directives
,
qq(SANew rules_filename "$opt->{configpath}")
if
defined
$opt
->{configpath};
push
@directives
,
qq(SANew site_rules_filename "$opt->{siteconfigpath}")
if
defined
$opt
->{siteconfigpath};
push
@directives
,
qq(SANew home_dir_for_helpers "$opt->{home_dir_for_helpers}")
if
defined
$opt
->{home_dir_for_helpers};
push
@directives
,
qq(SANew local_tests_only $opt->{local})
if
defined
$opt
->{
local
};
push
@directives
,
map
qq(SANew $_ "$opt->{$_}")
,
grep
defined
$opt
->{
$_
},
qw(PREFIX DEF_RULES_DIR LOCAL_RULES_DIR LOCAL_STATE_DIR)
;
push
@directives
,
'SANew paranoid 1'
if
$opt
->{paranoid};
push
@directives
,
qq(SAConfigLine "$_")
for
@{
$opt
->{cf} };
my
@users
;
push
@users
,
'local'
if
$opt
->{
'user-config'
};
push
@users
,
'sql'
if
$opt
->{
'sql-config'
};
push
@users
,
'ldap'
if
$opt
->{
'ldap-config'
};
push
@directives
,
join
' '
,
'SAUsers'
,
@users
if
@users
;
}
if
(
$opt
->{httpd_conf}) {
my
$fh
;
if
(
$opt
->{httpd_conf} eq
'-'
) {
open
$fh
,
'>&STDOUT'
or
die
"open >&STDOUT: $!"
;
}
else
{
open
$fh
,
'>'
,
$opt
->{httpd_conf}
or
die
"open >'$opt->{httpd_conf}': $!"
;
}
print
$fh
join
"\n"
,
"# generated by $0 on "
.
localtime
(
time
),
@directives
,
"# vim: filetype=apache\n"
;
close
$fh
or
warn
"close: $!"
;
exit
0;
}
push
@run
,
'-f'
,
File::Spec->devnull(),
map
{ ;
'-C'
=>
$_
}
@directives
;
warn
map
({ /^-/ ?
"\n $_"
:
" $_"
}
@run
),
"\n"
if
$opt
->{debug} =~ /\ball|spamd|config|info\b/;
undef
$opt
;
exec
@run
;
sub
get_libexecdir {
get_libexecdir_A2BC() || get_libexecdir_apxs();
}
sub
get_libexecdir_A2BC {
$INC
{
'Apache2/Build.pm'
}++;
my
$cfg
= Apache2::BuildConfig->new;
$cfg
->{APXS_LIBEXECDIR} ||
$cfg
->{MODPERL_APXS_LIBEXECDIR};
}
sub
get_libexecdir_apxs {
my
@cmd
= ((
$opt
->{apxs} ||
'apxs'
),
'-q'
,
'LIBEXECDIR'
);
chomp
(
my
$modpath
= get_cmd_output(
@cmd
));
die
"ERROR: failed to obtain module path from '@cmd'\n"
unless
length
$modpath
;
die
"ERROR: '$modpath' returned by '@cmd' is not an existing directory\n"
unless
-d
$modpath
;
$modpath
;
}
our
$apache_module_path
;
sub
apache_module_path {
my
$modname
=
shift
;
$apache_module_path
||= get_libexecdir();
my
$module
= File::Spec->catfile(
$apache_module_path
,
$modname
);
die
"ERROR: '$module' does not exist\n"
if
!-e
$module
;
$module
;
}
sub
static_apache_modules {
my
$httpd
=
shift
;
my
@cmd
= (
$httpd
,
'-l'
);
my
$out
= get_cmd_output(
@cmd
);
my
@modlist
=
$out
=~ /\b(\S+\.c)\b/gi;
die
"ERROR: failed to get list of static modules from '@cmd'\n"
unless
@modlist
;
@modlist
;
}
sub
get_cmd_output {
my
@cmd
=
@_
;
my
$output
= `
@cmd
` or
die
"ERROR: failed to run '@cmd': $!\n"
;
$output
;
}
sub
mpm_specific_config {
my
$mpm
=
shift
;
my
@ret
;
if
(
$mpm
=~ /^prefork|worker|beos|mpmt_os2$/) {
push
@ret
,
"User $opt->{username}"
if
$opt
->{username};
push
@ret
,
"Group $opt->{groupname}"
if
$opt
->{groupname};
}
elsif
(
$opt
->{username} ||
$opt
->{groupname}) {
die
"ERROR: username / groupname not supported with MPM $mpm\n"
;
}
if
(
$mpm
eq
'prefork'
) {
push
@ret
,
"StartServers $opt->{'min-spare'}"
;
push
@ret
,
"MinSpareServers $opt->{'min-spare'}"
;
push
@ret
,
"MaxSpareServers $opt->{'max-spare'}"
;
push
@ret
,
"MaxClients $opt->{'max-children'}"
;
}
elsif
(
$mpm
eq
'worker'
) {
push
@ret
,
grep
length
,
map
{ s/^\s+//; s/\s*\b
<<" EOF";
StartServers 1
ServerLimit 1
MinSpareThreads $opt->{'min-spare'}
MaxSpareThreads $opt->{'max-spare'}
ThreadLimit $opt->{'max-children'}
ThreadsPerChild $opt->{'max-children'}
EOF
}
else
{
warn
"WARNING: MPM $mpm not supported, using defaults for performance settings\n"
;
warn
"WARNING: prepare for huge memory usage and maybe an emergency reboot\n"
;
}
push
@ret
,
"MaxRequestsPerChild $opt->{'max-conn-per-child'}"
if
defined
$opt
->{
'max-conn-per-child'
};
@ret
;
}