Test syslogd logging to a tty which belongs to a user. This is
authorbluhm <bluhm@openbsd.org>
Mon, 19 Oct 2015 20:16:09 +0000 (20:16 +0000)
committerbluhm <bluhm@openbsd.org>
Mon, 19 Oct 2015 20:16:09 +0000 (20:16 +0000)
done with a utmp entry for a pty fake login.  All messages are read
from the pty and written into a log file.

31 files changed:
regress/usr.sbin/syslogd/Makefile
regress/usr.sbin/syslogd/README
regress/usr.sbin/syslogd/Syslogd.pm
regress/usr.sbin/syslogd/args-bufsize-native.pl
regress/usr.sbin/syslogd/args-bufsize-sendsyslog.pl
regress/usr.sbin/syslogd/args-bufsize-udp.pl
regress/usr.sbin/syslogd/args-bufsize-unix.pl
regress/usr.sbin/syslogd/args-client-tcp-close.pl
regress/usr.sbin/syslogd/args-client-tcp-error.pl
regress/usr.sbin/syslogd/args-client-tcp-maxline.pl
regress/usr.sbin/syslogd/args-client-tcp-multilines.pl
regress/usr.sbin/syslogd/args-client-tcp-nontransp-maxline.pl
regress/usr.sbin/syslogd/args-client-tcp-octet-maxline.pl
regress/usr.sbin/syslogd/args-client-tls-close.pl
regress/usr.sbin/syslogd/args-client-tls-error.pl
regress/usr.sbin/syslogd/args-client-tls-tcp.pl
regress/usr.sbin/syslogd/args-client-udp-nodns.pl
regress/usr.sbin/syslogd/args-client-udp.pl
regress/usr.sbin/syslogd/args-client-unix.pl
regress/usr.sbin/syslogd/args-default.pl
regress/usr.sbin/syslogd/args-dropped-sigterm-tcp.pl
regress/usr.sbin/syslogd/args-dropped-sigterm-tls.pl
regress/usr.sbin/syslogd/args-fdexhaustion-sighup.pl
regress/usr.sbin/syslogd/args-fdexhaustion-tcp.pl
regress/usr.sbin/syslogd/args-privsep-daemon.pl
regress/usr.sbin/syslogd/args-privsep-foreground.pl
regress/usr.sbin/syslogd/args-sigterm.pl
regress/usr.sbin/syslogd/args-tls-cafile-fake.pl
regress/usr.sbin/syslogd/args-ttymsg-wall.pl [new file with mode: 0644]
regress/usr.sbin/syslogd/funcs.pl
regress/usr.sbin/syslogd/ttylog.c [new file with mode: 0644]

index 8f538a9..66fd9ff 100644 (file)
@@ -1,4 +1,4 @@
-#      $OpenBSD: Makefile,v 1.12 2015/10/09 17:07:06 bluhm Exp $
+#      $OpenBSD: Makefile,v 1.13 2015/10/19 20:16:09 bluhm Exp $
 
 # The following ports must be installed for the regression tests:
 # p5-IO-Socket-INET6   object interface for AF_INET and AF_INET6 domain sockets
@@ -33,8 +33,9 @@ TARGETS ?=            ${ARGS}
 TARGETS ?=             ${ARGS:Nargs-rsyslog*}
 .endif
 REGRESS_TARGETS =      ${TARGETS:S/^/run-regress-/}
+LDFLAGS +=             -lutil
 CLEANFILES +=          *.log *.log.? *.conf ktrace.out stamp-*
-CLEANFILES +=          *.out *.sock *.ktrace *.fstat
+CLEANFILES +=          *.out *.sock *.ktrace *.fstat ttylog
 CLEANFILES +=          *.pem *.req *.crt *.key *.srl empty toobig
 
 .MAIN: all
@@ -99,6 +100,7 @@ ${REGRESS_TARGETS:M*tls*}: server.crt 127.0.0.1.crt
 ${REGRESS_TARGETS:M*empty*}: empty
 ${REGRESS_TARGETS:M*toobig*}: toobig
 ${REGRESS_TARGETS:M*fake*}: fake-ca.crt
+${REGRESS_TARGETS}: ttylog
 
 # make perl syntax check for all args files
 
index a263a58..ca26902 100644 (file)
@@ -7,9 +7,10 @@ to syslogd.  From there UDP transport is used to reach the server.
 All processes write log files where the message has to show up.
 The test arguments are kept in the args-*.pl files.
 The content of a log file, the data sent to a pipe process and what
-the server received are checked.  The invocation of the sendsyslog(2)
-syscall is checked with ktrace, the open file descriptors of syslogd
-are checked with fstat.
+the server received are checked.  Logging to a user's tty is tested
+with a fake login.  The invocation of the sendsyslog(2) syscall is
+checked with ktrace, the open file descriptors of syslogd are checked
+with fstat.
 When invoked with "make libevent", all tests are executed three
 times.  They pass the EVENT_NO...  environment variables over sudo
 into syslogd.  This way the select(2) and poll(2) and kqueue(2)
index e4e78cf..54e389b 100644 (file)
@@ -1,4 +1,4 @@
-#      $OpenBSD: Syslogd.pm,v 1.15 2015/08/25 20:52:44 bluhm Exp $
+#      $OpenBSD: Syslogd.pm,v 1.16 2015/10/19 20:16:09 bluhm Exp $
 
 # Copyright (c) 2010-2015 Alexander Bluhm <bluhm@openbsd.org>
 # Copyright (c) 2014 Florian Riehm <mail@friehm.de>
@@ -42,6 +42,7 @@ sub new {
        $args{conffile} ||= "syslogd.conf";
        $args{outfile} ||= "file.log";
        $args{outpipe} ||= "pipe.log";
+       $args{outtty} ||= "tty.log";
        if ($args{memory}) {
                $args{memory} = {} unless ref $args{memory};
                $args{memory}{name} ||= "memory";
@@ -67,6 +68,7 @@ sub new {
            or die ref($self), " create conf file $self->{conffile} failed: $!";
        print $fh "*.*\t$self->{outfile}\n";
        print $fh "*.*\t|dd of=$self->{outpipe}\n";
+       print $fh "*.*\tsyslogd-regress\n";
        my $memory = $self->{memory};
        print $fh "*.*\t:$memory->{size}:$memory->{name}\n" if $memory;
        my $loghost = $self->{loghost};
@@ -96,6 +98,17 @@ sub create_out {
        chmod(0666, $self->{outpipe})
            or die ref($self), " chmod pipe file $self->{outpipe} failed: $!";
 
+       unlink($self->{outtty});
+       open($fh, '>', $self->{outtty})
+           or die ref($self), " create tty file $self->{outtty} failed: $!";
+       close $fh;
+       my @sudo = $ENV{SUDO} ? $ENV{SUDO} : ();
+       my @cmd = (@sudo, "./ttylog", "syslogd-regress", $self->{outtty});
+       open($fh, '|-', @cmd)
+           or die ref($self), " pipe to ttylog $self->{outfile} failed: $!";
+       # remember until object is destroyed, perl autoclose will send EOF
+       $self->{fhtty} = $fh;
+
        return $self;
 }
 
@@ -157,6 +170,17 @@ sub up {
                    "after $timeout seconds";
                sleep .1;
        }
+
+       while ($self->{fhtty}) {
+               open(my $fh, '<', $self->{outtty}) or die ref($self),
+                   " open $self->{outtty} for reading failed: $!";
+               last if grep { /ttylog: started/ } <$fh>;
+               time() < $end
+                   or croak ref($self), " no 'started' in $self->{outtty} ".
+                   "after $timeout seconds";
+               sleep .1;
+       }
+
        return $self;
 }
 
index 666c6f8..b172d9a 100644 (file)
@@ -37,12 +37,11 @@ our %args = (
        # syslog over TCP appends a \n
        loggrep => { qr/^>>> 8193 .{8192}\n/ => 1 },
     },
-    pipe => {
-       nocheck => 1,
-    },
     file => {
        loggrep => { qr/^.{$filelen}\n/ => 1 },
     },
+    pipe => { nocheck => 1 },
+    tty => { nocheck => 1 },
 );
 
 1;
index 3160c5f..4401ab7 100644 (file)
@@ -38,12 +38,11 @@ our %args = (
        listen => { domain => AF_UNSPEC, proto => "tcp", addr => "localhost" },
        loggrep => { get_charlog() => 8 },
     },
-    pipe => {
-       nocheck => 1,
-    },
     file => {
        loggrep => { get_charlog() => 8 },
     },
+    pipe => { nocheck => 1 },
+    tty => { nocheck => 1 },
 );
 
 1;
index 3e6b0ec..8403fc6 100644 (file)
@@ -39,12 +39,11 @@ our %args = (
        listen => { domain => AF_UNSPEC, proto => "tcp", addr => "localhost" },
        loggrep => { get_charlog() => 8 },
     },
-    pipe => {
-       nocheck => 1,
-    },
     file => {
        loggrep => { get_charlog() => 8 },
     },
+    pipe => { nocheck => 1 },
+    tty => { nocheck => 1 },
 );
 
 1;
index d0661a4..1a604da 100644 (file)
@@ -38,12 +38,11 @@ our %args = (
        listen => { domain => AF_UNSPEC, proto => "tcp", addr => "localhost" },
        loggrep => { get_charlog() => 8 },
     },
-    pipe => {
-       nocheck => 1,
-    },
     file => {
        loggrep => { get_charlog() => 8 },
     },
+    pipe => { nocheck => 1 },
+    tty => { nocheck => 1 },
 );
 
 1;
index 174e658..3cca393 100644 (file)
@@ -38,14 +38,13 @@ our %args = (
        },
        loggrep => {},
     },
-    pipe => {
-       loggrep => {},
-    },
     file => {
        loggrep => {
            qr/syslogd: tcp logger .* connection close/ => 1,
        },
     },
+    pipe => { nocheck => 1 },
+    tty => { nocheck => 1 },
 );
 
 1;
index dd7c68d..ea1592c 100644 (file)
@@ -40,14 +40,13 @@ our %args = (
        },
        loggrep => {},
     },
-    pipe => {
-       loggrep => {},
-    },
     file => {
        loggrep => {
            qr/syslogd: tcp logger .* connection error: $errors/ => 1,
        },
     },
+    pipe => { nocheck => 1 },
+    tty => { nocheck => 1 },
 );
 
 1;
index 6f574ff..abd6a10 100644 (file)
@@ -55,7 +55,8 @@ our %args = (
            generate_chars(MAXLINE).qr/$/ => 2,
        },
     },
-    pipe => { loggrep => {} },  # XXX syslogd ignore short writes to pipe
+    pipe => { nocheck => 1 },  # XXX syslogd ignore short writes to pipe
+    tty => { nocheck => 1 },
 );
 
 1;
index 22d0f17..c15a838 100644 (file)
@@ -40,6 +40,7 @@ our %args = (
     server => { loggrep => \%threegrep },
     file => { loggrep => \%threegrep },
     pipe => { loggrep => \%threegrep },
+    tty => { loggrep => \%threegrep },
 );
 
 1;
index 05afaa2..622f48c 100644 (file)
@@ -57,7 +57,8 @@ our %args = (
            generate_chars(MAXLINE).qr/$/ => 2,
        },
     },
-    pipe => { loggrep => {} },  # XXX syslogd ignore short writes to pipe
+    pipe => { nocheck => 1 },  # XXX syslogd ignore short writes to pipe
+    tty => { nocheck => 1 },
 );
 
 1;
index a1e22d4..584733a 100644 (file)
@@ -57,7 +57,8 @@ our %args = (
            generate_chars(MAXLINE).qr/$/ => 2,
        },
     },
-    pipe => { loggrep => {} },  # XXX syslogd ignore short writes to pipe
+    pipe => { nocheck => 1 },  # XXX syslogd ignore short writes to pipe
+    tty => { nocheck => 1 },  # XXX syslogd ignore short writes to pipe
 );
 
 1;
index ac9a1b1..1af4105 100644 (file)
@@ -38,14 +38,13 @@ our %args = (
        },
        loggrep => {},
     },
-    pipe => {
-       loggrep => {},
-    },
     file => {
        loggrep => {
            qr/syslogd: tls logger .* connection close/ => 1,
        },
     },
+    pipe => { nocheck => 1 },
+    tty => { nocheck => 1 },
 );
 
 1;
index 12b67a0..0ed5f64 100644 (file)
@@ -40,15 +40,14 @@ our %args = (
        },
        loggrep => {},
     },
-    pipe => {
-       loggrep => {},
-    },
     file => {
        loggrep => {
            qr/syslogd: tls logger .* connection error: read failed: $errors/
                => 1,
        },
     },
+    pipe => { nocheck => 1, },
+    tty => { nocheck => 1, },
 );
 
 1;
index 5613fc2..a807047 100644 (file)
@@ -38,9 +38,6 @@ our %args = (
        },
        loggrep => {},
     },
-    pipe => {
-       loggrep => {},
-    },
     file => {
        loggrep => {
            qr/syslogd: tls logger .* connection error: /.
@@ -48,6 +45,8 @@ our %args = (
                qr/SSL routines:SSL23_GET_CLIENT_HELLO:unknown protocol/ => 1,
        },
     },
+    pipe => { nocheck => 1 },
+    tty => { nocheck => 1 },
 );
 
 1;
index 092444b..a82c603 100644 (file)
@@ -22,6 +22,9 @@ our %args = (
     pipe => {
        loggrep => get_testlog(),
     },
+    tty => {
+       loggrep => get_testlog(),
+    },
     file => {
        # Sys::Syslog UDP is broken, it appends a \n\0.
        loggrep => qr/ 127.0.0.1 syslogd-regress\[\d+\]: /.get_testlog().qr/ $/,
index faa7f52..1715261 100644 (file)
@@ -26,6 +26,9 @@ our %args = (
     pipe => {
        loggrep => get_testlog(),
     },
+    tty => {
+       loggrep => get_testlog(),
+    },
     file => {
        # Sys::Syslog UDP is broken, it appends a \n\0.
        loggrep => qr/ localhost syslogd-regress\[\d+\]: /.get_testlog().qr/ $/,
index 8327112..ea5f661 100644 (file)
@@ -24,6 +24,9 @@ our %args = (
     pipe => {
        loggrep => get_testlog(),
     },
+    tty => {
+       loggrep => get_testlog(),
+    },
     file => {
        # Sys::Syslog unix is broken, it appends a \n\0.
        loggrep => qr/ $host syslogd-regress\[\d+\]: /.get_testlog().qr/ $/,
index c37ce36..ed5af7e 100644 (file)
@@ -1,6 +1,6 @@
 # Test with default values, that is:
 # The client writes a message to Sys::Syslog native method.
-# The syslogd writes it into a file and through a pipe.
+# The syslogd writes it into a file and through a pipe and to tty.
 # The syslogd passes it via UDP to the loghost.
 # The server receives the message on its UDP socket.
 # Find the message in client, file, pipe, syslogd, server log.
index 8b54844..3b98829 100644 (file)
@@ -52,9 +52,6 @@ our %args = (
            get_charlog() => '>=10',
        },
     },
-    pipe => {
-       loggrep => {},
-    },
     file => {
        loggrep => {
            get_firstlog() => 1,
@@ -66,6 +63,8 @@ our %args = (
            qr/syslogd: dropped 2[0-9][0-9] messages to remote loghost/ => 1,
        },
     },
+    pipe => { nocheck => 1 },
+    tty => { nocheck => 1 },
 );
 
 1;
index b711ab9..20b1e9e 100644 (file)
@@ -53,9 +53,6 @@ our %args = (
            get_charlog() => '>=10',
        },
     },
-    pipe => {
-       loggrep => {},
-    },
     file => {
        loggrep => {
            get_firstlog() => 1,
@@ -67,6 +64,8 @@ our %args = (
            qr/syslogd: dropped 2[0-9][0-9] messages to remote loghost/ => 1,
        },
     },
+    pipe => { nocheck => 1 },
+    tty => { nocheck => 1 },
 );
 
 1;
index ea1e651..d0d56a8 100644 (file)
@@ -27,7 +27,7 @@ our %args = (
        loggrep => {
            # If not in startup, each failed PRIV_OPEN_LOG is logged
            # to tty, so PRIV_OPEN_TTY fails again.
-           qr/syslogd: receive_fd: recvmsg: Message too long/ => 4+2*3,
+           qr/syslogd: receive_fd: recvmsg: Message too long/ => '>='.(4+2*3),
            # During first initialization the lockpipe is open.  When
            # SIGHUP happens it is closed and one more file can be opened.
            qr/X FILE:/ => 1+16+1+17,
@@ -52,6 +52,12 @@ our %args = (
        (map { { loggrep => get_testgrep() } } 0..16),
        (map { { loggrep => { qr/./s => 0 } } } 17..19),
     ],
+    tty => {
+       loggrep => {
+           get_firstlog() => 1,
+           get_testlog() => 0,
+       }
+    }
 );
 
 1;
index 2224100..256e030 100644 (file)
@@ -43,7 +43,8 @@ our %args = (
     file => {
        loggrep => qr/syslogd: accept deferred: Too many open files/,
     },
-    pipe => { loggrep => {} },
+    pipe => { nocheck => 1 },
+    tty => { nocheck => 1 },
 );
 
 1;
index 6c8f814..771713b 100644 (file)
@@ -37,9 +37,8 @@ our %args = (
            qr/RET   setsid.* errno / => 0,
        },
     },
-    pipe => {
-       nocheck => 1,
-    },
+    pipe => { nocheck => 1 },
+    tty => { nocheck => 1 },
 );
 
 1;
index 33053a8..dfffd19 100644 (file)
@@ -36,9 +36,8 @@ our %args = (
            qr/CALL  setsid/ => 0,
        },
     },
-    pipe => {
-       nocheck => 1,
-    },
+    pipe => { nocheck => 1 },
+    tty => { nocheck => 1 },
 );
 
 1;
index ef3d62b..ac04148 100644 (file)
@@ -42,6 +42,7 @@ our %args = (
     },
     file => { loggrep => (get_between2loggrep())[0] },
     pipe => { loggrep => (get_between2loggrep())[0] },
+    tty => { loggrep => (get_between2loggrep())[0] },
 );
 
 1;
index 53120aa..2c07c19 100644 (file)
@@ -17,7 +17,7 @@ our %args = (
            qr/Logging to FORWTLS \@tls:\/\/localhost:\d+/ => '>=4',
            qr/syslogd: loghost .* connection error: /.
                qr/handshake failed: error:.*/.
-               qr/RSA_padding_check_PKCS1_type_1:block type is not 01/ => 2,
+               qr/RSA_EAY_PUBLIC_DECRYPT:data too large for modulus/ => 2,
            get_testgrep() => 1,
        },
        cacrt => "fake-ca.crt",
diff --git a/regress/usr.sbin/syslogd/args-ttymsg-wall.pl b/regress/usr.sbin/syslogd/args-ttymsg-wall.pl
new file mode 100644 (file)
index 0000000..606d318
--- /dev/null
@@ -0,0 +1,30 @@
+# The client writes a message to Sys::Syslog native method.
+# The syslogd writes it into a file and through a pipe and to tty.
+# The syslogd passes it via UDP to the loghost.
+# The server receives the message on its UDP socket.
+# Find the message in client, file, pipe, syslogd, server log.
+
+use strict;
+use warnings;
+use Sys::Syslog qw(:macros);
+
+our %args = (
+    client => {
+       func => sub {
+           my $self = shift;
+           syslog(LOG_LOCAL5|LOG_ERR, "test message to all users");
+           write_log($self);
+       },
+    },
+    syslogd => {
+       conf => "local5.err\t*",
+    },
+    tty => {
+       loggrep => {
+           qr/Message from syslogd/ => 1,
+           qr/syslogd-regress.* test message to all users/ => 2,
+       },
+    },
+);
+
+1;
index 2eeead9..fc70628 100644 (file)
@@ -1,4 +1,4 @@
-#      $OpenBSD: funcs.pl,v 1.25 2015/10/09 17:07:06 bluhm Exp $
+#      $OpenBSD: funcs.pl,v 1.26 2015/10/19 20:16:09 bluhm Exp $
 
 # Copyright (c) 2010-2015 Alexander Bluhm <bluhm@openbsd.org>
 #
@@ -245,7 +245,7 @@ sub get_testlog {
 }
 
 sub get_testgrep {
-       return qr/$testlog$/;
+       return qr/$testlog\r*$/;
 }
 
 sub get_firstlog {
@@ -351,7 +351,7 @@ sub check_out {
                $r->loggrep("bytes transferred", 1) or sleep 1;
        }
 
-       foreach my $name (qw(file pipe)) {
+       foreach my $name (qw(file pipe tty)) {
                next if $args{$name}{nocheck};
                my $file = $r->{"out$name"} or die;
                my $pattern = $args{$name}{loggrep} || get_testgrep();
diff --git a/regress/usr.sbin/syslogd/ttylog.c b/regress/usr.sbin/syslogd/ttylog.c
new file mode 100644 (file)
index 0000000..d87d863
--- /dev/null
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2015 Alexander Bluhm <bluhm@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/ioctl.h>
+#include <sys/sockio.h>
+
+#include <errno.h>
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <termios.h>
+#include <time.h>
+#include <unistd.h>
+#include <util.h>
+#include <utmp.h>
+
+__dead void usage(void);
+void timeout(int);
+void terminate(int);
+void iostdin(int);
+
+FILE *lg;
+char *tty;
+
+__dead void
+usage()
+{
+       fprintf(stderr, "usage: %s username logfile\n", getprogname());
+       exit(2);
+}
+
+int
+main(int argc, char *argv[])
+{
+       char buf[8192], ptyname[16], *username, *logfile;
+       struct utmp utmp;
+       int mfd, sfd;
+       ssize_t n;
+       int i;
+
+       if (argc != 3)
+               usage();
+       username = argv[1];
+       logfile = argv[2];
+
+       if ((lg = fopen(logfile, "w")) == NULL)
+               err(1, "fopen %s", logfile);
+       if (setlinebuf(lg) != 0)
+               err(1, "setlinebuf");
+
+       if (signal(SIGTERM, terminate) == SIG_ERR)
+               err(1, "signal SIGTERM");
+       if (signal(SIGINT, terminate) == SIG_ERR)
+               err(1, "signal SIGINT");
+
+       if (openpty(&mfd, &sfd, ptyname, NULL, NULL) == -1)
+               err(1, "openpty");
+       fprintf(lg, "openpty %s\n", ptyname);
+       if ((tty = strrchr(ptyname, '/')) == NULL)
+               errx(1, "tty: %s", ptyname);
+       tty++;
+
+       /* login(3) searches for a controlling tty, use the created one */
+       if (dup2(sfd, 1) == -1)
+               err(1, "dup2 stdout");
+
+       memset(&utmp, 0, sizeof(utmp));
+       strlcpy(utmp.ut_line, tty, sizeof(utmp.ut_line));
+       strlcpy(utmp.ut_name, username, sizeof(utmp.ut_name));
+       time(&utmp.ut_time);
+       login(&utmp);
+       fprintf(lg, "login %s %s\n", username, tty);
+
+       if (signal(SIGIO, iostdin) == SIG_ERR)
+               err(1, "signal SIGIO");
+       if (setpgid(0, 0) == -1)
+               err(1, "setpgid");
+       i = getpid();
+       if (fcntl(0, F_SETOWN, i) == -1 &&
+           ioctl(0, SIOCSPGRP, &i) == -1)  /* pipe(2) with F_SETOWN broken */
+               err(1, "fcntl F_SETOWN, ioctl SIOCSPGRP");
+       if (fcntl(0, F_SETFL, O_ASYNC) == -1)
+               err(1, "fcntl O_ASYNC");
+
+       if (signal(SIGALRM, timeout) == SIG_ERR)
+               err(1, "signal SIGALRM");
+       if (alarm(30) == (unsigned int)-1)
+               err(1, "alarm");
+
+       fprintf(lg, "%s: started\n", getprogname());
+
+       while ((n = read(mfd, buf, sizeof(buf))) > 0) {
+               fprintf(lg, ">>> ");
+               if (fwrite(buf, 1, n, lg) != (size_t)n)
+                       err(1, "fwrite %s", logfile);
+               if (buf[n-1] != '\n')
+                       fprintf(lg, "\n");
+       }
+       if (n < 0)
+               err(1, "read %s", ptyname);
+       fprintf(lg, "EOF %s\n", ptyname);
+
+       if (logout(tty) == 0)
+               errx(1, "logout %s", tty);
+       fprintf(lg, "logout %s\n", tty);
+
+       errx(3, "EOF");
+}
+
+void
+timeout(int sig)
+{
+       fprintf(lg, "signal timeout %d\n", sig);
+       if (tty) {
+               logout(tty);
+               fprintf(lg, "logout %s\n", tty);
+       }
+       errx(3, "timeout");
+}
+
+void
+terminate(int sig)
+{
+       fprintf(lg, "signal terminate %d\n", sig);
+       if (tty) {
+               logout(tty);
+               fprintf(lg, "logout %s\n", tty);
+       }
+       errx(3, "terminate");
+}
+
+void
+iostdin(int sig)
+{
+       char buf[8192];
+       size_t n;
+
+       fprintf(lg, "signal iostdin %d\n", sig);
+       if ((n = read(0, buf, sizeof(buf))) < 0)
+               err(1, "read stdin");
+       if (tty) {
+               logout(tty);
+               fprintf(lg, "logout %s\n", tty);
+       }
+       if (n > 0)
+               errx(3, "read stdin %zd bytes", n);
+       exit(0);
+}