xref: /openbsd-src/regress/usr.sbin/syslogd/Syslogd.pm (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
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