use
POSIX
qw(:sys_wait_h setsid sigprocmask)
;
our
@ISA
= ();
our
%prio_map
= (
dbg
=>
'debug'
,
debug
=>
'debug'
,
info
=>
'info'
,
notice
=>
'notice'
,
warn
=>
'warning'
,
warning
=>
'warning'
,
error
=>
'err'
,
err
=>
'err'
,
crit
=>
'crit'
,
alert
=>
'alert'
,
emerg
=>
'emerg'
);
our
$syslog_open
= 0;
sub
new {
my
$class
=
shift
;
$class
=
ref
(
$class
) ||
$class
;
my
$self
= { };
bless
(
$self
,
$class
);
$self
->{already_done_failure_warning} = 0;
$self
->{disabled} = 0;
$self
->{consecutive_failures} = 0;
$self
->{failure_threshold} = 10;
$self
->{SIGPIPE_RECEIVED} = 0;
my
%params
=
@_
;
$self
->{ident} =
$params
{ident} ||
'spamassassin'
;
$self
->{log_socket} =
$params
{
socket
};
$self
->{log_facility} =
$params
{facility};
$self
->{timestamp_fmt} =
$params
{timestamp_fmt};
$self
->{escape} =
$params
{escape}
if
exists
$params
{escape};
if
(!
$self
->init()) {
die
"logger: syslog initialization failed\n"
;
}
return
(
$self
);
}
sub
init {
my
(
$self
) =
@_
;
my
$log_socket
=
$self
->{log_socket};
$log_socket
=
''
if
!
defined
$log_socket
;
my
$eval_stat
;
eval
{
if
(
$log_socket
eq
''
) {
}
else
{
dbg(
"logger: calling setlogsock($log_socket)"
);
setlogsock(
$log_socket
) or
die
"setlogsock($log_socket) failed: $!"
;
}
dbg(
"logger: opening syslog with $log_socket socket"
);
openlog(
$self
->{ident},
'cons,pid,ndelay'
,
$self
->{log_facility});
$syslog_open
= 1;
1;
} or
do
{
$eval_stat
= $@ ne
''
? $@ :
"errno=$!"
;
chomp
$eval_stat
;
dbg(
"logger: connection to syslog/$log_socket failed: $eval_stat"
);
};
if
(
defined
(
$eval_stat
) &&
$log_socket
ne
'inet'
) {
dbg(
"logger: trying setlogsock('inet')"
);
undef
$eval_stat
;
eval
{
setlogsock(
'inet'
) or
die
"setlogsock('inet') failed: $!"
;
dbg(
"logger: opening syslog using inet socket"
);
openlog(
$self
->{ident},
'cons,pid,ndelay'
,
$self
->{log_facility});
$syslog_open
= 1;
1;
} or
do
{
$eval_stat
= $@ ne
''
? $@ :
"errno=$!"
;
chomp
$eval_stat
;
dbg(
"logger: connection to syslog/inet failed: $eval_stat"
);
};
}
if
(
defined
$eval_stat
) {
return
0;
}
else
{
dbg(
"logger: successfully connected to syslog/$log_socket"
);
return
1;
}
}
sub
log_message {
my
(
$self
,
$level
,
$msg
,
$ts
) =
@_
;
return
if
$self
->{disabled};
$level
=
$prio_map
{
$level
};
if
(!
defined
$level
) {
$level
=
'err'
;
$msg
=
'(bad prio: '
.
$_
[1] .
') '
.
$msg
;
}
if
(
$self
->{escape}) {
Mail::SpamAssassin::Logger::escape_str(
$msg
);
}
elsif
(!
exists
$self
->{escape}) {
$msg
=~
tr
/\x09\x20\x00-\x1f/ _/s;
}
local
$SIG
{
'PIPE'
} =
sub
{
$self
->{SIGPIPE_RECEIVED}++;
eval
{ closelog() }
if
$syslog_open
;
$syslog_open
= 0;
};
my
$timestamp
=
''
;
my
$fmt
=
$self
->{timestamp_fmt};
if
(
defined
$fmt
&&
$fmt
ne
''
) {
my
$now
=
defined
$ts
?
$ts
: Time::HiRes::
time
;
$timestamp
= POSIX::strftime(
$fmt
,
localtime
(
$now
));
}
$msg
=
$timestamp
.
' '
.
$msg
if
$timestamp
ne
''
;
my
$eval_stat
;
eval
{
syslog(
$level
,
"%s"
,
$msg
); 1;
} or
do
{
$eval_stat
= $@ ne
''
? $@ :
"errno=$!"
;
chomp
$eval_stat
;
};
if
(
defined
$eval_stat
) {
if
(
$self
->check_syslog_sigpipe(
$msg
)) {
}
else
{
warn
"logger: syslog failed: $eval_stat\n"
;
if
(!
$self
->{already_done_failure_warning}) {
warn
"logger: try using --syslog-socket={unix,inet} or --syslog=file\n"
;
$self
->{already_done_failure_warning} = 1;
}
}
$self
->syslog_incr_failure_counter();
}
else
{
$self
->{consecutive_failures} = 0;
$self
->check_syslog_sigpipe(
$msg
);
}
$SIG
{PIPE} =
'IGNORE'
;
}
sub
check_syslog_sigpipe {
my
(
$self
,
$msg
) =
@_
;
if
(!
$self
->{SIGPIPE_RECEIVED}) {
return
0;
}
eval
{
closelog()
if
$syslog_open
;
$syslog_open
= 0;
openlog(
$self
->{ident},
'cons,pid,ndelay'
,
$self
->{log_facility});
$syslog_open
= 1;
syslog(
'debug'
,
"%s"
,
"syslog reopened"
);
syslog(
'info'
,
"%s"
,
$msg
);
$msg
=
"SIGPIPE received, reopening log socket"
;
dbg(
"log: $msg"
);
syslog(
'info'
,
"%s"
,
$msg
);
if
(
$self
->{SIGPIPE_RECEIVED}) {
warn
"logger: syslog failure: multiple SIGPIPEs received\n"
;
$self
->{disabled} = 1;
}
$self
->{SIGPIPE_RECEIVED} = 0;
return
1;
1;
} or
do
{
my
$eval_stat
= $@ ne
''
? $@ :
"errno=$!"
;
chomp
$eval_stat
;
dbg(
"log: failure in check_syslog_sigpipe: $eval_stat"
);
$self
->syslog_incr_failure_counter();
}
}
sub
syslog_incr_failure_counter {
my
(
$self
) =
@_
;
$self
->{consecutive_failures}++;
if
(
$self
->{consecutive_failures}++ >
$self
->{failure_threshold}) {
warn
(
"logger: syslog() failed "
.
$self
->{consecutive_failures} .
" times in a row, disabled\n"
);
$self
->{disabled} = 1;
return
1;
}
return
0;
}
sub
close_log {
my
(
$self
) =
@_
;
closelog()
if
$syslog_open
;
$syslog_open
= 0;
}
1;