1# $OpenBSD: Syslogd.pm,v 1.19 2016/07/12 15:44:58 bluhm Exp $ 2 3# Copyright (c) 2010-2015 Alexander Bluhm <bluhm@openbsd.org> 4# Copyright (c) 2014 Florian Riehm <mail@friehm.de> 5# 6# Permission to use, copy, modify, and distribute this software for any 7# purpose with or without fee is hereby granted, provided that the above 8# copyright notice and this permission notice appear in all copies. 9# 10# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 18use strict; 19use warnings; 20 21package Syslogd; 22use parent 'Proc'; 23use Carp; 24use Cwd; 25use File::Basename; 26use Sys::Hostname; 27use Time::HiRes qw(time alarm sleep); 28 29sub new { 30 my $class = shift; 31 my %args = @_; 32 $args{ktracefile} ||= "syslogd.ktrace"; 33 $args{fstatfile} ||= "syslogd.fstat"; 34 $args{logfile} ||= "syslogd.log"; 35 $args{up} ||= "syslogd: started"; 36 $args{down} ||= "syslogd: exiting"; 37 $args{up} = $args{down} = "execute:" 38 if $args{foreground} || $args{daemon}; 39 $args{foreground} && $args{daemon} 40 and croak "$class cannot run in foreground and as daemon"; 41 $args{func} = sub { Carp::confess "$class func may not be called" }; 42 $args{conffile} ||= "syslogd.conf"; 43 $args{outfile} ||= "file.log"; 44 $args{outpipe} ||= "pipe.log"; 45 $args{outconsole} ||= "console.log"; 46 $args{outuser} ||= "user.log"; 47 if ($args{memory}) { 48 $args{memory} = {} unless ref $args{memory}; 49 $args{memory}{name} ||= "memory"; 50 $args{memory}{size} //= 1; 51 } 52 my $self = Proc::new($class, %args); 53 $self->{connectaddr} 54 or croak "$class connect addr not given"; 55 56 _make_abspath(\$self->{$_}) foreach (qw(conffile outfile outpipe)); 57 58 # substitute variables in config file 59 my $curdir = dirname($0) || "."; 60 my $objdir = getcwd(); 61 my $hostname = hostname(); 62 (my $host = $hostname) =~ s/\..*//; 63 my $connectdomain = $self->{connectdomain}; 64 my $connectaddr = $self->{connectaddr}; 65 my $connectproto = $self->{connectproto}; 66 my $connectport = $self->{connectport}; 67 68 open(my $fh, '>', $self->{conffile}) 69 or die ref($self), " create conf file $self->{conffile} failed: $!"; 70 print $fh "*.*\t$self->{outfile}\n"; 71 print $fh "*.*\t|dd of=$self->{outpipe}\n"; 72 print $fh "*.*\t/dev/console\n"; 73 print $fh "*.*\tsyslogd-regress\n"; 74 my $memory = $self->{memory}; 75 print $fh "*.*\t:$memory->{size}:$memory->{name}\n" if $memory; 76 my $loghost = $self->{loghost}; 77 unless ($loghost) { 78 $loghost = '@$connectaddr'; 79 $loghost .= ':$connectport' if $connectport; 80 } 81 my $config = "*.*\t$loghost\n"; 82 $config .= $self->{conf} if $self->{conf}; 83 $config =~ s/(\$[a-z]+)/$1/eeg; 84 print $fh $config; 85 close $fh; 86 87 return $self->create_out(); 88} 89 90sub create_out { 91 my $self = shift; 92 my @sudo = $ENV{SUDO} ? $ENV{SUDO} : (); 93 94 open(my $fh, '>', $self->{outfile}) 95 or die ref($self), " create log file $self->{outfile} failed: $!"; 96 close $fh; 97 98 open($fh, '>', $self->{outpipe}) 99 or die ref($self), " create pipe file $self->{outpipe} failed: $!"; 100 close $fh; 101 chmod(0666, $self->{outpipe}) 102 or die ref($self), " chmod pipe file $self->{outpipe} failed: $!"; 103 104 foreach my $dev (qw(console user)) { 105 my $file = $self->{"out$dev"}; 106 unlink($file); 107 open($fh, '>', $file) 108 or die ref($self), " create $dev file $file failed: $!"; 109 close $fh; 110 my $user = $dev eq "console" ? 111 "/dev/console" : "syslogd-regress"; 112 my @cmd = (@sudo, "./ttylog", $user, $file); 113 $self->{"pid$dev"} = open(my $ctl, '|-', @cmd) 114 or die ref($self), " pipe to @cmd failed: $!"; 115 # remember until object is destroyed, autoclose will send EOF 116 $self->{"ctl$dev"} = $ctl; 117 } 118 119 return $self; 120} 121 122sub ttykill { 123 my $self = shift; 124 my $dev = shift; 125 my $sig = shift; 126 my $pid = $self->{"pid$dev"} 127 or die ref($self), " no tty log pid$dev"; 128 129 if (kill($sig => $pid) != 1) { 130 my $sudo = $ENV{SUDO}; 131 $sudo && $!{EPERM} 132 or die ref($self), " kill $pid failed: $!"; 133 my @cmd = ($sudo, '/bin/kill', "-$sig", $pid); 134 system(@cmd) 135 and die ref($self), " sudo kill $pid failed: $?"; 136 } 137 return $self; 138} 139 140sub child { 141 my $self = shift; 142 my @sudo = $ENV{SUDO} ? $ENV{SUDO} : "env"; 143 144 my @pkill = (@sudo, "pkill", "-KILL", "-x", "syslogd"); 145 my @pgrep = ("pgrep", "-x", "syslogd"); 146 system(@pkill) && $? != 256 147 and die ref($self), " system '@pkill' failed: $?"; 148 while ($? == 0) { 149 print STDERR "syslogd still running\n"; 150 system(@pgrep) && $? != 256 151 and die ref($self), " system '@pgrep' failed: $?"; 152 } 153 print STDERR "syslogd not running\n"; 154 155 my @libevent; 156 foreach (qw(EVENT_NOKQUEUE EVENT_NOPOLL EVENT_NOSELECT)) { 157 push @libevent, "$_=1" if delete $ENV{$_}; 158 } 159 push @libevent, "EVENT_SHOW_METHOD=1" if @libevent; 160 my @ktrace = $ENV{KTRACE} || (); 161 @ktrace = "ktrace" if $self->{ktrace} && !@ktrace; 162 push @ktrace, "-i", "-f", $self->{ktracefile} if @ktrace; 163 my $syslogd = $ENV{SYSLOGD} ? $ENV{SYSLOGD} : "syslogd"; 164 my @cmd = (@sudo, @libevent, @ktrace, $syslogd, 165 "-f", $self->{conffile}); 166 push @cmd, "-d" if !$self->{foreground} && !$self->{daemon}; 167 push @cmd, "-F" if $self->{foreground}; 168 push @cmd, "-V" unless $self->{cacrt}; 169 push @cmd, "-C", $self->{cacrt} 170 if $self->{cacrt} && $self->{cacrt} ne "default"; 171 push @cmd, "-s", $self->{ctlsock} if $self->{ctlsock}; 172 push @cmd, @{$self->{options}} if $self->{options}; 173 print STDERR "execute: @cmd\n"; 174 exec @cmd; 175 die ref($self), " exec '@cmd' failed: $!"; 176} 177 178sub up { 179 my $self = Proc::up(shift, @_); 180 my $timeout = shift || 10; 181 182 my $end = time() + $timeout; 183 184 while ($self->{fstat}) { 185 $self->fstat(); 186 last unless $self->{foreground} || $self->{daemon}; 187 188 # in foreground mode and as daemon we have no debug output 189 # check fstat kqueue entry to detect statup 190 open(my $fh, '<', $self->{fstatfile}) or die ref($self), 191 " open $self->{fstatfile} for reading failed: $!"; 192 last if grep { /kqueue/ } <$fh>; 193 time() < $end 194 or croak ref($self), " no 'kqueue' in $self->{fstatfile} ". 195 "after $timeout seconds"; 196 sleep .1; 197 } 198 199 foreach my $dev (qw(console user)) { 200 my $file = $self->{"out$dev"}; 201 while ($self->{"ctl$dev"}) { 202 open(my $fh, '<', $file) or die ref($self), 203 " open $file for reading failed: $!"; 204 last if grep { /ttylog: started/ } <$fh>; 205 time() < $end 206 or croak ref($self), " no 'started' in $file ". 207 "after $timeout seconds"; 208 sleep .1; 209 } 210 } 211 212 return $self; 213} 214 215sub fstat { 216 my $self = shift; 217 218 open(my $fh, '>', $self->{fstatfile}) or die ref($self), 219 " open $self->{fstatfile} for writing failed: $!"; 220 my @cmd = ("fstat"); 221 open(my $fs, '-|', @cmd) 222 or die ref($self), " open pipe from '@cmd' failed: $!"; 223 print $fh grep { /^\w+ *syslogd *\d+/ } <$fs>; 224 close($fs) or die ref($self), $! ? 225 " close pipe from '@cmd' failed: $!" : 226 " command '@cmd' failed: $?"; 227 close($fh) 228 or die ref($self), " close $self->{fstatfile} failed: $!"; 229} 230 231sub _make_abspath { 232 my $file = ref($_[0]) ? ${$_[0]} : $_[0]; 233 if (substr($file, 0, 1) ne "/") { 234 $file = getcwd(). "/". $file; 235 ${$_[0]} = $file if ref($_[0]); 236 } 237 return $file; 238} 239 240sub kill_privsep { 241 return Proc::kill(@_); 242} 243 244sub kill_syslogd { 245 my $self = shift; 246 my $sig = shift // 'TERM'; 247 my $ppid = shift // $self->{pid}; 248 249 # find syslogd child of privsep parent 250 my @cmd = ("ps", "-ww", "-p", $ppid, "-U", "_syslogd", 251 "-o", "pid,ppid,comm", ); 252 open(my $ps, '-|', @cmd) 253 or die ref($self), " open pipe from '@cmd' failed: $!"; 254 my @pslist; 255 my @pshead = split(' ', scalar <$ps>); 256 while (<$ps>) { 257 s/\s+$//; 258 my %h; 259 @h{@pshead} = split(' ', $_, scalar @pshead); 260 push @pslist, \%h; 261 } 262 close($ps) or die ref($self), $! ? 263 " close pipe from '@cmd' failed: $!" : 264 " command '@cmd' failed: $?"; 265 my @pschild = 266 grep { $_->{PPID} == $ppid && $_->{COMMAND} eq "syslogd" } @pslist; 267 @pschild == 1 268 or die ref($self), " not one privsep child: ", 269 join(" ", map { $_->{PID} } @pschild); 270 271 return Proc::kill($self, $sig, $pschild[0]{PID}); 272} 273 274my $rotate_num = 0; 275sub rotate { 276 my $self = shift; 277 278 $self->loggrep("bytes transferred", 1) or sleep 1; 279 foreach my $name (qw(file pipe)) { 280 my $file = $self->{"out$name"}; 281 for (my $i = $rotate_num; $i >= 0; $i--) { 282 my $new = $file. ".$i"; 283 my $old = $file. ($i > 0 ? ".".($i-1) : ""); 284 285 rename($old, $new) or die ref($self), 286 " rename from '$old' to '$new' failed: $!"; 287 } 288 } 289 $rotate_num++; 290 return $self->create_out(); 291}; 292 2931; 294