App-Syslogd
view release on metacpan or search on metacpan
lib/App/Syslogd.pm view on Meta::CPAN
}
# ---------------------------------------------------------------------------
# Private and protected helpers
# ---------------------------------------------------------------------------
# _receive
# Purpose: wait for one datagram.
# Entry: $self->{socket} set; $buffer_ref a scalar ref to fill.
# Premise 1: _receive is only called from run()'s loop.
# Premise 2: run() calls open_socket() before the loop, and
# open_socket() dies if it cannot give a socket. Conclusion:
# the socket always exists here, so it is not checked again.
# Exit: the sender's sockaddr, or undef if interrupted or on error.
# Side Effects: carps on errors other than EINTR.
sub _receive
{
my ($self, $buffer_ref) = @_;
my $peer = $self->{socket}->recv(${$buffer_ref}, $RECV_BUFFER);
# EINTR is how a signal wakes us; it is expected, not an error
if(!defined($peer) && !$!{EINTR}) {
carp($self->i18n('recv_failed', { error => "$!" }));
}
return $peer;
}
# _open_log
# Purpose: open $self->{file} for appending, safely.
# Entry: $self->{file} set.
# Exit: an open filehandle (written with syswrite, unbuffered);
# croaks on failure.
# Side Effects: may create the file (mode 0600) and write the header row;
# chmods an existing file to 0600.
sub _open_log
{
my $self = shift;
my $file = $self->{file};
# The eval below must not touch the caller's $@
local $@;
# O_NOFOLLOW: if the log is in a shared directory (/tmp), anyone could plant a
# symlink to /etc/shadow before root starts us. O_APPEND: rows from
# one write() are never interleaved with another writer's, and always
# go to the end. O_RDWR (not O_WRONLY) so the first line can be read
# to check the file is one of our logs.
my $fh;
{
no autodie qw(sysopen);
sysopen($fh, $file, O_RDWR | O_APPEND | O_CREAT | $O_NOFOLLOW | $O_NONBLOCK, $LOG_MODE)
or croak($self->i18n('open_failed', { file => $file, error => "$!" }));
}
# O_NOFOLLOW cannot catch a hard link or a file someone else pre-created
# for us to fill with their reading material, so check after opening
# Safe means all three: a regular file, owned by us, one link. By De
# Morgan, "not all three" is "any one of them fails".
my @st = stat($fh);
unless(-f _ && ($st[4] == $>) && ($st[3] == 1)) {
_discard($fh);
croak($self->i18n('unsafe_file', { file => $file }));
}
# A file that already has content must be one of our logs: it must
# start with the header line. Without this a mistaken setting (as
# root, every root-owned file is "ours") would append to, and chmod,
# any file at all, /etc/passwd included. Checked before the chmod, so
# a file that is not ours to change is never changed.
unless(-z _ || $self->_starts_with_header($fh)) {
_discard($fh);
croak($self->i18n('not_a_log', { file => $file }));
}
# Tighten an existing file too: previous versions created it 0644
chmod($LOG_MODE, $fh) if($HAVE_FCHMOD);
binmode($fh);
# Header only on an empty file, so a reopened file is not given a
# second header row half way down. If that fails the handle is
# closed here, not left for the garbage collector.
if(-z $fh) {
eval { $self->_write_header($fh); 1 } or do {
my $error = $@;
_discard($fh);
die $error;
};
}
return $fh;
}
# _starts_with_header
# Purpose: is this file one of our logs?
# Entry: $fh open for reading on a non-empty file.
# Exit: 1 if the first line is the header row (ending in "\n", or
# "\r\n" for a file once written on Windows), else 0.
# Side Effects: moves the read position; writes are unaffected, because
# O_APPEND always writes at the end.
sub _starts_with_header
{
my ($self, $fh) = @_;
no autodie qw(sysseek sysread);
# The header exactly as _write_header writes it, without its newline
my ($line) = $self->_csv_line([@CSV_HEADER]);
(my $header = $line // '') =~ s/\n\z//;
# Read the header and up to two line-end bytes; a failed seek or read
# leaves $start empty, which does not match
my $start = '';
sysread($fh, $start, length($header) + 2) if(defined(sysseek($fh, 0, SEEK_SET)));
return (length($header) && $start =~ /\A\Q$header\E\r?\n/) ? 1 : 0;
}
# _discard
# Purpose: close a handle that is being abandoned because of an error.
( run in 0.464 second using v1.01-cache-2.11-cpan-cb04b646d8c )