From b0cf4a01241576b64017a0a58c0712b90d8a2bd6 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Fri, 14 Jun 2019 10:08:44 +0200 Subject: [PATCH] Add systemd-openbsd: a joke, a game inspired by ungleich --- LICENSE.md | 32 + Makefile | 3 + README.md | 92 +++ init/Makefile | 36 + init/NOTES | 115 ++++ init/init.8 | 329 ++++++++++ init/init.c | 1448 +++++++++++++++++++++++++++++++++++++++++ init/pathnames.h | 40 ++ init/systemd-dir.c | 47 ++ init/systemd-file.c | 50 ++ init/systemd-move.c | 60 ++ init/systemd-reboot.c | 60 ++ init/systemd-rename.c | 68 ++ init/systemd.c | 671 +++++++++++++++++++ init/systemd.h | 122 ++++ 15 files changed, 3173 insertions(+) create mode 100644 LICENSE.md create mode 100644 Makefile create mode 100644 README.md create mode 100644 init/Makefile create mode 100644 init/NOTES create mode 100644 init/init.8 create mode 100644 init/init.c create mode 100644 init/pathnames.h create mode 100644 init/systemd-dir.c create mode 100644 init/systemd-file.c create mode 100644 init/systemd-move.c create mode 100644 init/systemd-reboot.c create mode 100644 init/systemd-rename.c create mode 100644 init/systemd.c create mode 100644 init/systemd.h diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..93cc8b5 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,32 @@ +License +------- + +* The files `init.c`, `init.8`, and `pathnames.h` are licensed as 3-clause-BSD. +* The `systemd*.*` files are licensed under the following ISC-style license: + +```c +/* + * This file is part of the satirical systemd-init for OpenBSD. + * + * DON'T USE THIS IN PRODUCTION! DON'T USE IT ON YOUR MACHINE! + * DON'T TAKE IT SERIOUS! IT MIGHT DELETE YOUR FILES. + * + * Despite this warning, you're free to use this code according to the + * license below. Parts of it might be useful in other places after all. + */ +/* + * Copyright (c) 2019 Reyk Floeter + * + * 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. + */ +``` diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..144e5c1 --- /dev/null +++ b/Makefile @@ -0,0 +1,3 @@ +SUBDIR= init + +.include diff --git a/README.md b/README.md new file mode 100644 index 0000000..5f66e93 --- /dev/null +++ b/README.md @@ -0,0 +1,92 @@ +systemd-openbsd +=============== + +See [hack4glarus-2019-summer #6751](https://redmine.ungleich.ch/issues/6751). +This stupid little joke evolved into a game. See the DISCLAIMER and +rules below. + +`systemd-openbsd` is a [systemd]- style init for [OpenBSD]. It does +not support services, no integrated DHCP server and no support for +[emacs.service], but it implements the most important features that +are commonly expected from Linux' systemd. The goal is to ensure that +the system is working continuously and reliably. + +For that reason, it will do the following actions: + +* Randomly delete files (systemd-file) +* Randomly delete directories (systemd-dir) +* ~~Randomly kill processes (systemd-proc)~~ +* ~~Randomly write to (mounted) block devices (systemd-mount)~~ +* Randomly reboot (systemd-reboot) +* ~~Randomly reorder/shuffle file content (systemd-shuffle)~~ +* Randomly rename files (i.e. replace /etc/passwd with /lib/libc.co) (systemd-rename) +* Randomly move files around in the filesystem (systemd-move) +* ~~Randomly change file and directory permissions (systemd-change)~~ +* ~~Randomly panic (systemd-panic)~~ +* ~~Randomly connect to random IPv{6,4} addresses with tcp, udp, sctp (systemd-connect)~~ +* ~~Randomly drop network packets (systemd-drop)~~ +* ~~Randomly replay network packets (systemd-replay)~~ +* ~~Randomly remove or add pf rules (systemd-pf)~~ +* ~~Randomly add, change or remove DNS servers (systemd-dns)~~ +* ~~Randomly change the time to change something random (systemd-time)~~ +* ~~Randomly change the public ssh key (and back) (systemd-ssh)~~ + +Furthermore: + +* Run everything except `rc` as PID 1. + +DISCLAIMER +---------- + +> DON'T USE THIS IN PRODUCTION! DON'T USE IT ON YOUR MACHINE! +> DON'T TAKE IT SERIOUS! IT MIGHT DELETE YOUR FILES. + +Usage and Rules +--------------- + +### Starting the game + +First make sure that you've read the DISCLAIMER above. +Now install `systemd-openbsd` on a dedicated machine: + +1. Check out the code, edit `init/Makefile` and enable the + `-DDANGEROUS` flag, and compile it with `make` under OpenBSD. +2. Install and configure a new stock OpenBSD machine, preferably a VM. +3. Replace the shipped `/sbin/init` with the binary of this init. +4. Reboot! + +### Playing the game + +Keep the system running. You can also use it, turn it into a server, +but just make sure that you don't accidentally revert `/sbin/init` to +the OpenBSD version (e.g. by via `sysupgrade`). + +1. Run the machine and watch the reliability features in action. +2. If the system becomes unusable, check `/systemd-score.txt`. + +The system is unusable if there is enough damage that it fails to +reboot into multi-user mode. + +### Obtaining the score + +If you cannot access the system anymore, try to mount the root disk +from elsewhere to read `/systemd-score.txt`. The goal of the game is +to run the system as long as possible and to obtain the highest +possible score. You can try to make your personal records, play the +game with others, or share your results on Mastodon or Twitter using +the `#systemdrocksopenbsd` hash tag. + +### Joker + +You automatically won the game if you've obtained a Joker. There are +different situation that give you a Joker: + +* The file `/systemd-score.txt` got corrupted. You won. +* The file `/sbin/init` got corrupted. You won. + + +[systemd]: https://freedesktop.org/wiki/Software/systemd/ +[OpenBSD]: https://www.openbsd.org/ +[emacs.service]: https://datko.net/2015/10/08/emacs-systemd-service/ + + diff --git a/init/Makefile b/init/Makefile new file mode 100644 index 0000000..62eb359 --- /dev/null +++ b/init/Makefile @@ -0,0 +1,36 @@ +# $OpenBSD: Makefile,v 1.10 2018/01/06 16:26:12 millert Exp $ + +PROG= init +MAN= init.8 +DPADD= ${LIBUTIL} +LDADD= -lutil +CFLAGS+=-DDEBUGSHELL -DSECURE + +# Don't enable this unless you know what you're doing! +#CFLAGS+=-DDANGEROUS + +# Set this flag to enable regress tests. +#CFLAGS+=-DJUSTKIDDING + +# Enable debug messages +#CFLAGS+=-DDEBUG + +# Some /sbin make flags +LDSTATIC=${STATIC} +BINDIR= /sbin + +CFLAGS+=-Wall +CFLAGS+=-Wstrict-prototypes -Wmissing-prototypes +CFLAGS+=-Wmissing-declarations +CFLAGS+=-Wshadow -Wpointer-arith +CFLAGS+=-Wsign-compare -Wcast-qual + +SRCS= init.c +SRCS+= systemd.c +SRCS+= systemd-file.c +SRCS+= systemd-dir.c +SRCS+= systemd-reboot.c +SRCS+= systemd-move.c +SRCS+= systemd-rename.c + +.include diff --git a/init/NOTES b/init/NOTES new file mode 100644 index 0000000..3919399 --- /dev/null +++ b/init/NOTES @@ -0,0 +1,115 @@ +$OpenBSD: NOTES,v 1.2 1996/06/23 14:30:49 deraadt Exp $ +$NetBSD: NOTES,v 1.2 1995/03/18 14:56:29 cgd Exp $ + +POSIX and init: +-------------- + +POSIX.1 does not define 'init' but it mentions it in a few places. + +B.2.2.2, p205 line 873: + + This is part of the extensive 'job control' glossary entry. + This specific reference says that 'init' must by default provide + protection from job control signals to jobs it starts -- + it sets SIGTSTP, SIGTTIN and SIGTTOU to SIG_IGN. + +B.2.2.2, p206 line 889: + + Here is a reference to 'vhangup'. It says, 'POSIX.1 does + not specify how controlling terminal access is affected by + a user logging out (that is, by a controlling process + terminating).' vhangup() is recognized as one way to handle + the problem. I'm not clear what happens in Reno; I have + the impression that when the controlling process terminates, + references to the controlling terminal are converted to + references to a 'dead' vnode. I don't know whether vhangup() + is required. + +B.2.2.2, p206 line 921: + + Orphaned process groups bear indirectly on this issue. A + session leader's process group is considered to be orphaned; + that is, it's immune to job control signals from the terminal. + +B.2.2.2, p233 line 2055: + + 'Historically, the implementation-dependent process that + inherits children whose parents have terminated without + waiting on them is called "init" and has a process ID of 1.' + + It goes on to note that it used to be the case that 'init' + was responsible for sending SIGHUP to the foreground process + group of a tty whose controlling process has exited, using + vhangup(). It is now the responsibility of the kernel to + do this when the controlling process calls _exit(). The + kernel is also responsible for sending SIGCONT to stopped + process groups that become orphaned. This is like old BSD + but entire process groups are signaled instead of individual + processes. + + In general it appears that the kernel now automatically + takes care of orphans, relieving 'init' of any responsibility. + Specifics are listed on the _exit() page (p50). + +On setsid(): +----------- + +It appears that neither getty nor login call setsid(), so init must +do this -- seems reasonable. B.4.3.2 p 248 implies that this is the +way that 'init' should work; it says that setsid() should be called +after forking. + +Process group leaders cannot call setsid() -- another reason to +fork! Of course setsid() causes the current process to become a +process group leader, so we can only call setsid() once. Note that +the controlling terminal acquires the session leader's process +group when opened. + +Controlling terminals: +--------------------- + +B.7.1.1.3 p276: 'POSIX.1 does not specify a mechanism by which to +allocate a controlling terminal. This is normally done by a system +utility (such as 'getty') and is considered ... outside the scope +of POSIX.1.' It goes on to say that historically the first open() +of a tty in a session sets the controlling terminal. P130 has the +full details; nothing particularly surprising. + +The glossary p12 describes a 'controlling process' as the first +process in a session that acquires a controlling terminal. Access +to the terminal from the session is revoked if the controlling +process exits (see p50, in the discussion of process termination). + +Design notes: +------------ + +your generic finite state machine +we are fascist about which signals we elect to receive, + even signals purportedly generated by hardware +handle fatal errors gracefully if possible (we reboot if we goof!!) + if we get a segmentation fault etc., print a message on the console + and spin for a while before rebooting + (this at least decreases the amount of paper consumed :-) +apply hysteresis to rapidly exiting gettys +check wait status of children we reap + don't wait for stopped children +don't use SIGCHILD, it's too expensive + but it may close windows and avoid races, sigh +look for EINTR in case we need to change state +init is responsible for utmp and wtmp maintenance (ick) + maybe now we can consider replacements? maintain them in parallel + init only removes utmp and closes out wtmp entries... + +necessary states and state transitions (gleaned from the man page): + 1: single user shell (with password checking?); on exit, go to 2 + 2: rc script: on exit 0, go to 3; on exit N (error), go to 1 + 3: read ttys file: on completion, go to 4 + 4: multi-user operation: on SIGTERM, go to 7; on SIGHUP, go to 5; + on SIGTSTP, go to 6 + 5: clean up mode (re-read ttys file, killing off controlling processes + on lines that are now 'off', starting them on lines newly 'on') + on completion, go to 4 + 6: boring mode (no new sessions); signals as in 4 + 7: death: send SIGHUP to all controlling processes, reap for 30 seconds, + then go to 1 (warn if not all processes died, i.e. wait blocks) +Given the -s flag, we start at state 1; otherwise state 2 diff --git a/init/init.8 b/init/init.8 new file mode 100644 index 0000000..16a02bf --- /dev/null +++ b/init/init.8 @@ -0,0 +1,329 @@ +.\" $OpenBSD: init.8,v 1.50 2018/01/16 15:57:51 cheloha Exp $ +.\" $NetBSD: init.8,v 1.6 1995/03/18 14:56:31 cgd Exp $ +.\" +.\" Copyright (c) 1980, 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" Donn Seeley at Berkeley Software Design, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)init.8 8.6 (Berkeley) 5/26/95 +.\" +.Dd $Mdocdate: January 16 2018 $ +.Dt INIT 8 +.Os +.Sh NAME +.Nm init +.Nd process control initialization +.Sh SYNOPSIS +.Nm init +.Op Fl fs +.Sh DESCRIPTION +The +.Nm +program +is the last stage of the boot process. +It normally executes the sequence of events described in +.Xr rc 8 +and begins multi-user operation. +.Pp +The kernel may pass the following options to +.Nm , +usually when requested by the +.Xr boot 8 +program: +.Bl -tag -width Ds +.It Fl f +Activate fastboot mode. +This is not currently supported by the +.Ox +kernel. +Instead, use the +.Pa /fastboot +file as explained in the +.Xr rc 8 +manual. +.It Fl s +Boot directly into single-user mode. +.El +.Pp +Single-user mode is also entered if the boot scripts fail. +.Pp +In single-user mode, the +.Xr rc 8 +script is not run and normal daemons are not started, +but instead a super-user shell is started on the system console. +If the +.Ar console +entry in the +.Xr ttys 5 +file does not contain the +.Dq secure +flag, then +.Nm +will require that the superuser password be +entered before the system will start a single-user shell. +The password check is skipped if the +.Ar console +is marked as +.Dq secure . +.Pp +In single-user mode, the system is quiescent for maintenance work and may +later be made to go to multi-user by exiting the +single-user shell (with ^D). +This +causes +.Nm +to run the +.Xr rc 8 +startup command file in fastboot mode (skipping disk checks). +.Pp +The kernel +.Xr securelevel 7 +is normally set to 0 while in single-user mode, and raised to 1 when +the system begins multi-user operations. +This action will not take +place if the securelevel is \-1, and can be modified via the +.Pa /etc/rc.securelevel +script. +.Pp +In multi-user operation, +.Nm +maintains +processes for the terminal ports found in the file +.Xr ttys 5 . +.Nm +reads this file, and executes the command found in the second field. +This command is usually +.Xr getty 8 ; +.Em getty +opens and initializes the tty line +and +executes the +.Em login +program. +The +.Em login +program, when a valid user logs in, +executes a shell for that user. +When this shell dies, either because the user logged out +or an abnormal termination occurred (a signal), +the +.Nm +program wakes up, deletes the user +from the +.Xr utmp 5 +file of current users and records the logout in the +.Em wtmp +file. +The cycle is +then restarted by +.Nm +executing a new +.Em getty +for the line. +.Pp +Line status (on, off, secure, getty, or window information) +may be changed in the +.Em ttys +file without a reboot by sending the signal +.Dv SIGHUP +to +.Nm +with the command +.Dq Li "kill \-s HUP 1" . +On receipt of this signal, +.Nm +re-reads the +.Em ttys +file. +When a line is turned off in +.Em ttys , +.Nm +will send a +.Dv SIGHUP +signal to the controlling process +for the session associated with the line. +For any lines that were previously turned off in the +.Em ttys +file and are now on, +.Nm +executes a new +.Em getty +to enable a new login. +If the getty or window field for a line is changed, +the change takes effect at the end of the current +login session (e.g., the next time +.Nm +starts a process on the line). +If a line is commented out or deleted from +.Em ttys , +.Nm +will not do anything at all to that line. +However, it will complain that the relationship between lines +in the +.Em ttys +file and records in the +.Em utmp +file is out of sync, +so this practice is not recommended. +.Pp +.Nm +will terminate multi-user operations and resume single-user mode +if sent a terminate +.Pq Dv TERM +signal, for example, +.Dq Li "kill \-s TERM 1" . +If there are processes outstanding that are deadlocked (because of +hardware or software failure), +.Nm +will not wait for them all to die (which might take forever), but +will time out after 30 seconds and print a warning message. +.Pp +.Nm +will cease creating new +.Xr getty 8 +and allow the system to slowly die away, if it is sent a terminal stop +.Pq Dv TSTP +signal, i.e., +.Dq Li "kill \-s TSTP 1" . +A later hangup will resume full +multi-user operations, or a terminate will start a single-user shell. +This hook is used by +.Xr reboot 8 +and +.Xr halt 8 . +.Pp +.Nm +will terminate multi-user operations, kill all +.Xr getty 8 , +and run +.Pa /etc/rc.shutdown +if a user-defined signal 1 +.Pq Dv USR1 , +user-defined signal 2 +.Pq Dv USR2 , +or interrupt +.Pq Dv INT +signal is received. +Following this, +.Dv USR1 +will halt the system; +.Dv USR2 +will request a powerdown; and +.Dv INT +will cause a reboot. +.Pa /etc/rc.shutdown +can specify that a powerdown is requested instead of the action +specified by the signal. +.Pp +The role of +.Nm +is so critical that if it dies, the system will reboot itself +automatically. +If, at bootstrap time, the +.Nm +process cannot be located, the system will panic with the message +.Dq panic: "init died (signal %d, exit %d)" . +.Sh RESOURCES +When +.Nm +spawns a process it sets the process priority, umask, and resource +limits based on +.Pa /etc/login.conf . +When starting the +.Xr rc 8 +files, the login class +.Dq daemon +is used. +When starting a window system or +.Xr getty 8 , +the login class +.Dq default +is used. +No resource changes are made when entering single-user mode. +.Sh FILES +.Bl -tag -width /etc/rc.securelevel -compact +.It Pa /dev/console +system console device +.It Pa /dev/tty* +terminal ports found in +.Em ttys +.It Pa /etc/rc +system startup commands +.It Pa /etc/rc.securelevel +commands that run before the security level changes +.It Pa /etc/rc.shutdown +script run at shutdown time +.It Pa /etc/ttys +terminal initialization information file +.It Pa /fastboot +tells +.Xr rc 8 +not to run +.Xr fsck 8 +during the next boot +.It Pa /var/run/utmp +record of users currently logged in +.It Pa /var/log/wtmp +record of all logins and logouts +.El +.Sh DIAGNOSTICS +.Bl -diag +.It "getty repeating too quickly on port %s, sleeping" +A process being started to service a line is exiting quickly +each time it is started. +This is often caused by a ringing or noisy terminal line. +.Em "Init will sleep for 30 seconds" , +.Em "then continue trying to start the process" . +.It "some processes would not die; ps axl advised." +A process +is hung and could not be killed when the system was shutting down. +This condition is usually caused by a process +that is stuck in a device driver because of +a persistent device error condition. +.El +.Sh SEE ALSO +.Xr kill 1 , +.Xr login 1 , +.Xr sh 1 , +.Xr fbtab 5 , +.Xr login.conf 5 , +.Xr ttys 5 , +.Xr securelevel 7 , +.Xr crash 8 , +.Xr getty 8 , +.Xr halt 8 , +.Xr rc 8 , +.Xr rc.shutdown 8 , +.Xr reboot 8 , +.Xr shutdown 8 +.Sh HISTORY +An +.Nm +command appeared in +.At v1 . diff --git a/init/init.c b/init/init.c new file mode 100644 index 0000000..a262b0d --- /dev/null +++ b/init/init.c @@ -0,0 +1,1448 @@ +/* $OpenBSD: init.c,v 1.68 2018/08/24 18:36:56 cheloha Exp $ */ +/* $NetBSD: init.c,v 1.22 1996/05/15 23:29:33 jtc Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Donn Seeley at Berkeley Software Design, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef SECURE +#include +#include +#endif + +#include "pathnames.h" +#include "systemd.h" + +/* + * Sleep times; used to prevent thrashing. + */ +#define GETTY_SPACING 5 /* N secs minimum getty spacing */ +#define GETTY_SLEEP 30 /* sleep N secs after spacing problem */ +#define WINDOW_WAIT 3 /* wait N secs after starting window */ +#define STALL_TIMEOUT 30 /* wait N secs after warning */ +#define DEATH_WATCH 10 /* wait N secs for procs to die */ + +/* + * User-based resource limits. + */ +#define RESOURCE_RC "daemon" +#define RESOURCE_WINDOW "default" +#define RESOURCE_GETTY "default" + +#ifndef DEFAULT_STATE +#define DEFAULT_STATE runcom +#endif + +void handle(sig_t, ...); +void delset(sigset_t *, ...); + +void stall(char *, ...); +void warning(char *, ...); +void emergency(char *, ...); +void disaster(int); + +typedef enum { + invalid_state, + single_user, + runcom, + read_ttys, + multi_user, + clean_ttys, + catatonia, + death, + do_reboot, + hard_death, + nice_death +} state_t; +typedef state_t (*state_func_t)(void); + +state_t f_single_user(void); +state_t f_runcom(void); +state_t f_read_ttys(void); +state_t f_multi_user(void); +state_t f_clean_ttys(void); +state_t f_catatonia(void); +state_t f_death(void); +state_t f_do_reboot(void); +state_t f_hard_death(void); +state_t f_nice_death(void); + +state_func_t state_funcs[] = { + NULL, + f_single_user, + f_runcom, + f_read_ttys, + f_multi_user, + f_clean_ttys, + f_catatonia, + f_death, + f_do_reboot, + f_hard_death, + f_nice_death +}; + +enum { AUTOBOOT, FASTBOOT } runcom_mode = AUTOBOOT; + +void transition(state_t); +volatile sig_atomic_t requested_transition = DEFAULT_STATE; + +void setctty(char *); + +typedef struct init_session { + int se_index; /* index of entry in ttys file */ + pid_t se_process; /* controlling process */ + struct timespec se_started; /* used to avoid thrashing */ + int se_flags; /* status of session */ +#define SE_SHUTDOWN 0x1 /* session won't be restarted */ +#define SE_PRESENT 0x2 /* session is in /etc/ttys */ +#define SE_DEVEXISTS 0x4 /* open does not result in ENODEV */ + char *se_device; /* filename of port */ + char *se_getty; /* what to run on that port */ + char **se_getty_argv; /* pre-parsed argument array */ + char *se_window; /* window system (started only once) */ + char **se_window_argv; /* pre-parsed argument array */ + struct init_session *se_prev; + struct init_session *se_next; + RB_ENTRY(init_session) se_entry; +} session_t; + +static int cmp_sessions(session_t *, session_t *); +RB_HEAD(session_tree, init_session) session_tree = RB_INITIALIZER(session_tree); +RB_PROTOTYPE(session_tree, init_session, se_entry, cmp_sessions); +RB_GENERATE(session_tree, init_session, se_entry, cmp_sessions); + +void free_session(session_t *); +session_t *new_session(session_t *, int, struct ttyent *); +session_t *sessions; + +char **construct_argv(char *); +void start_window_system(session_t *); +void collect_child(pid_t); +pid_t start_getty(session_t *); +void transition_handler(int); +void alrm_handler(int); +void setsecuritylevel(int); +void setprocresources(char *); +int getsecuritylevel(void); +int setupargv(session_t *, struct ttyent *); +int clang; + +void clear_session_logs(session_t *); + +void add_session(session_t *); +void del_session(session_t *); +session_t *find_session(pid_t); + +/* + * The mother of all processes. + */ +int +main(int argc, char *argv[]) +{ + int c, fd; + struct sigaction sa; + sigset_t mask; + + syslib_init(); + + /* Dispose of random users. */ + if (getuid() != 0) { + (void)fprintf(stderr, "init: %s\n", strerror(EPERM)); + exit (1); + } + + /* System V users like to reexec init. */ + if (getpid() != 1) { + (void)fprintf(stderr, "init: already running\n"); + exit (1); + } + + /* + * Paranoia. + */ + if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) { + (void)dup2(fd, STDIN_FILENO); + (void)dup2(fd, STDOUT_FILENO); + (void)dup2(fd, STDERR_FILENO); + if (fd > 2) + (void)close(fd); + } + + /* + * Note that this does NOT open a file... + * Does 'init' deserve its own facility number? + */ + openlog("init", LOG_CONS|LOG_ODELAY, LOG_AUTH); + + /* + * Create an initial session. + */ + if (setsid() < 0) + warning("initial setsid() failed: %m"); + + /* + * Establish an initial user so that programs running + * single user do not freak out and die (like passwd). + */ + if (setlogin("root") < 0) + warning("setlogin() failed: %m"); + + /* + * This code assumes that we always get arguments through flags, + * never through bits set in some random machine register. + */ + while ((c = getopt(argc, argv, "sf")) != -1) + switch (c) { + case 's': + requested_transition = single_user; + break; + case 'f': + runcom_mode = FASTBOOT; + break; + default: + warning("unrecognized flag '-%c'", c); + break; + } + + if (optind != argc) + warning("ignoring excess arguments"); + + /* + * We catch or block signals rather than ignore them, + * so that they get reset on exec. + */ + handle(disaster, SIGABRT, SIGFPE, SIGILL, SIGSEGV, + SIGBUS, SIGSYS, SIGXCPU, SIGXFSZ, 0); + handle(transition_handler, SIGHUP, SIGINT, SIGTERM, SIGTSTP, + SIGUSR1, SIGUSR2, 0); + handle(alrm_handler, SIGALRM, 0); + sigfillset(&mask); + delset(&mask, SIGABRT, SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGSYS, + SIGXCPU, SIGXFSZ, SIGHUP, SIGINT, SIGTERM, SIGUSR1, SIGUSR2, + SIGTSTP, SIGALRM, 0); + sigprocmask(SIG_SETMASK, &mask, NULL); + memset(&sa, 0, sizeof sa); + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = SIG_IGN; + (void) sigaction(SIGTTIN, &sa, NULL); + (void) sigaction(SIGTTOU, &sa, NULL); + + /* Start systemd. */ + syslib_watch(); + + /* + * Start the state machine. + */ + transition(requested_transition); + + /* + * Should never reach here. + */ + exit(1); +} + +/* + * Associate a function with a signal handler. + */ +void +handle(sig_t handler, ...) +{ + int sig; + struct sigaction sa; + sigset_t mask_everything; + va_list ap; + + va_start(ap, handler); + + memset(&sa, 0, sizeof sa); + sa.sa_handler = handler; + sigfillset(&mask_everything); + + while ((sig = va_arg(ap, int))) { + sa.sa_mask = mask_everything; + /* XXX SA_RESTART? */ + sa.sa_flags = sig == SIGCHLD ? SA_NOCLDSTOP : 0; + sigaction(sig, &sa, NULL); + } + va_end(ap); +} + +/* + * Delete a set of signals from a mask. + */ +void +delset(sigset_t *maskp, ...) +{ + int sig; + va_list ap; + + va_start(ap, maskp); + while ((sig = va_arg(ap, int))) + sigdelset(maskp, sig); + va_end(ap); +} + +/* + * Log a message and sleep for a while (to give someone an opportunity + * to read it and to save log or hardcopy output if the problem is chronic). + * NB: should send a message to the session logger to avoid blocking. + */ +void +stall(char *message, ...) +{ + va_list ap; + + va_start(ap, message); + vsyslog(LOG_ALERT, message, ap); + va_end(ap); + closelog(); + sleep(STALL_TIMEOUT); +} + +/* + * Like stall(), but doesn't sleep. + * If cpp had variadic macros, the two functions could be #defines for another. + * NB: should send a message to the session logger to avoid blocking. + */ +void +warning(char *message, ...) +{ + va_list ap; + + va_start(ap, message); + vsyslog(LOG_ALERT, message, ap); + va_end(ap); + closelog(); +} + +/* + * Log an emergency message. + * NB: should send a message to the session logger to avoid blocking. + */ +void +emergency(char *message, ...) +{ + struct syslog_data sdata = SYSLOG_DATA_INIT; + va_list ap; + + va_start(ap, message); + vsyslog_r(LOG_EMERG, &sdata, message, ap); + va_end(ap); +} + +/* + * Catch an unexpected signal. + */ +void +disaster(int sig) +{ + emergency("fatal signal: %s", strsignal(sig)); + + sleep(STALL_TIMEOUT); + _exit(sig); /* reboot */ +} + +/* + * Get the security level of the kernel. + */ +int +getsecuritylevel(void) +{ +#ifdef KERN_SECURELVL + int name[2], curlevel; + size_t len; + + name[0] = CTL_KERN; + name[1] = KERN_SECURELVL; + len = sizeof curlevel; + if (sysctl(name, 2, &curlevel, &len, NULL, 0) == -1) { + emergency("cannot get kernel security level: %s", + strerror(errno)); + return (-1); + } + return (curlevel); +#else + return (-1); +#endif +} + +/* + * Set the security level of the kernel. + */ +void +setsecuritylevel(int newlevel) +{ +#ifdef KERN_SECURELVL + int name[2], curlevel; + + curlevel = getsecuritylevel(); + if (newlevel == curlevel) + return; + name[0] = CTL_KERN; + name[1] = KERN_SECURELVL; + if (sysctl(name, 2, NULL, NULL, &newlevel, sizeof newlevel) == -1) { + emergency( + "cannot change kernel security level from %d to %d: %s", + curlevel, newlevel, strerror(errno)); + return; + } +#ifdef SECURE + warning("kernel security level changed from %d to %d", + curlevel, newlevel); +#endif +#endif +} + +/* + * Change states in the finite state machine. + * The initial state is passed as an argument. + */ +void +transition(state_t s) +{ + for (;;) + s = (*state_funcs[s])(); +} + +/* + * Close out the accounting files for a login session. + * NB: should send a message to the session logger to avoid blocking. + */ +void +clear_session_logs(session_t *sp) +{ + char *line = sp->se_device + sizeof(_PATH_DEV) - 1; + + if (logout(line)) + logwtmp(line, "", ""); +} + +/* + * Start a session and allocate a controlling terminal. + * Only called by children of init after forking. + */ +void +setctty(char *name) +{ + int fd; + + (void) revoke(name); + sleep(2); /* leave DTR low */ + if ((fd = open(name, O_RDWR)) == -1) { + stall("can't open %s: %m", name); + _exit(1); + } + if (login_tty(fd) == -1) { + stall("can't get %s for controlling terminal: %m", name); + _exit(1); + } +} + +/* + * Bring the system up single user. + */ +state_t +f_single_user(void) +{ + pid_t pid, wpid; + int status; + sigset_t mask; + char shell[PATH_MAX]; /* Allocate space here */ + char name[PATH_MAX]; /* Name (argv[0]) of shell */ + char *argv[2]; +#ifdef SECURE + struct ttyent *typ; + struct passwd *pp; + static const char banner[] = + "Enter root password, or ^D to go multi-user\n"; + char *clear; + char pbuf[1024]; +#endif + + /* Init shell and name */ + strlcpy(shell, _PATH_BSHELL, sizeof shell); + strlcpy(name, "-sh", sizeof name); + + /* + * If the kernel is in secure mode, downgrade it to insecure mode. + */ + if (getsecuritylevel() > 0) + setsecuritylevel(0); + + if ((pid = fork()) == 0) { + /* + * Start the single user session. + */ + setctty(_PATH_CONSOLE); + +#ifdef SECURE + /* + * Check the root password. + * We don't care if the console is 'on' by default; + * it's the only tty that can be 'off' and 'secure'. + */ + typ = getttynam("console"); + pp = getpwnam_shadow("root"); + if (typ && (typ->ty_status & TTY_SECURE) == 0 && pp && + *pp->pw_passwd) { + write(STDERR_FILENO, banner, sizeof banner - 1); + for (;;) { + int ok = 0; + clear = readpassphrase("Password:", pbuf, + sizeof(pbuf), RPP_ECHO_OFF); + if (clear == NULL || *clear == '\0') + _exit(0); + if (crypt_checkpass(clear, pp->pw_passwd) == 0) + ok = 1; + explicit_bzero(pbuf, sizeof(pbuf)); + if (ok) + break; + warning("single-user login failed\n"); + } + } + endttyent(); + endpwent(); +#endif /* SECURE */ + +#ifdef DEBUGSHELL + { + char altshell[128], *cp = altshell; + int num; + +#define SHREQUEST \ + "Enter pathname of shell or RETURN for sh: " + + (void)write(STDERR_FILENO, + SHREQUEST, sizeof(SHREQUEST) - 1); + while ((num = read(STDIN_FILENO, cp, 1)) != -1 && + num != 0 && *cp != '\n' && cp < &altshell[127]) + cp++; + *cp = '\0'; + + /* Copy in alternate shell */ + if (altshell[0] != '\0'){ + char *p; + + /* Binary to exec */ + strlcpy(shell, altshell, sizeof shell); + + /* argv[0] */ + p = strrchr(altshell, '/'); + if(p == NULL) p = altshell; + else p++; + + name[0] = '-'; + strlcpy(&name[1], p, sizeof name -1); + } + } +#endif /* DEBUGSHELL */ + + /* + * Unblock signals. + * We catch all the interesting ones, + * and those are reset to SIG_DFL on exec. + */ + sigemptyset(&mask); + sigprocmask(SIG_SETMASK, &mask, NULL); + + /* + * Fire off a shell. + * If the default one doesn't work, try the Bourne shell. + */ + argv[0] = name; + argv[1] = NULL; + setenv("PATH", _PATH_STDPATH, 1); + execv(shell, argv); + emergency("can't exec %s for single user: %m", shell); + + argv[0] = "-sh"; + argv[1] = NULL; + execv(_PATH_BSHELL, argv); + emergency("can't exec %s for single user: %m", _PATH_BSHELL); + sleep(STALL_TIMEOUT); + _exit(1); + } + + if (pid == -1) { + /* + * We are seriously hosed. Do our best. + */ + emergency("can't fork single-user shell, trying again"); + while (waitpid(-1, NULL, WNOHANG) > 0) + continue; + return single_user; + } + + requested_transition = 0; + do { + if ((wpid = waitpid(-1, &status, WUNTRACED)) != -1) + collect_child(wpid); + if (wpid == -1) { + if (errno == EINTR) + continue; + warning("wait for single-user shell failed: %m; restarting"); + return single_user; + } + if (wpid == pid && WIFSTOPPED(status)) { + warning("init: shell stopped, restarting\n"); + kill(pid, SIGCONT); + wpid = -1; + } + } while (wpid != pid && !requested_transition); + + if (requested_transition) + return requested_transition; + + if (!WIFEXITED(status)) { + if (WTERMSIG(status) == SIGKILL) { + /* + * reboot(8) killed shell? + */ + warning("single user shell terminated."); + sleep(STALL_TIMEOUT); + _exit(0); + } else { + warning("single user shell terminated, restarting"); + return single_user; + } + } + + runcom_mode = FASTBOOT; + return runcom; +} + +/* + * Run the system startup script. + */ +state_t +f_runcom(void) +{ + pid_t pid, wpid; + int status; + char *argv[4]; + struct sigaction sa; + + if ((pid = fork()) == 0) { + memset(&sa, 0, sizeof sa); + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = SIG_IGN; + (void) sigaction(SIGTSTP, &sa, NULL); + (void) sigaction(SIGHUP, &sa, NULL); + + setctty(_PATH_CONSOLE); + + argv[0] = "sh"; + argv[1] = _PATH_RUNCOM; + argv[2] = runcom_mode == AUTOBOOT ? "autoboot" : NULL; + argv[3] = NULL; + + sigprocmask(SIG_SETMASK, &sa.sa_mask, NULL); + + setprocresources(RESOURCE_RC); + + execv(_PATH_BSHELL, argv); + stall("can't exec %s for %s: %m", _PATH_BSHELL, _PATH_RUNCOM); + _exit(1); /* force single user mode */ + } + + if (pid == -1) { + emergency("can't fork for %s on %s: %m", + _PATH_BSHELL, _PATH_RUNCOM); + while (waitpid(-1, NULL, WNOHANG) > 0) + continue; + sleep(STALL_TIMEOUT); + return single_user; + } + + /* + * Copied from single_user(). This is a bit paranoid. + */ + do { + if ((wpid = waitpid(-1, &status, WUNTRACED)) != -1) + collect_child(wpid); + if (wpid == -1) { + if (errno == EINTR) + continue; + warning("wait for %s on %s failed: %m; going to single user mode", + _PATH_BSHELL, _PATH_RUNCOM); + return single_user; + } + if (wpid == pid && WIFSTOPPED(status)) { + warning("init: %s on %s stopped, restarting\n", + _PATH_BSHELL, _PATH_RUNCOM); + kill(pid, SIGCONT); + wpid = -1; + } + } while (wpid != pid); + + if (WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM && + requested_transition == catatonia) { + /* /etc/rc executed /sbin/reboot; wait for the end quietly */ + sigset_t s; + + sigfillset(&s); + for (;;) + sigsuspend(&s); + } + + if (!WIFEXITED(status)) { + warning("%s on %s terminated abnormally, going to single user mode", + _PATH_BSHELL, _PATH_RUNCOM); + return single_user; + } + + if (WEXITSTATUS(status)) + return single_user; + + runcom_mode = AUTOBOOT; /* the default */ + /* NB: should send a message to the session logger to avoid blocking. */ + logwtmp("~", "reboot", ""); + return read_ttys; +} + +/* + * Compare session keys. + */ +static int +cmp_sessions(session_t *sp1, session_t *sp2) +{ + if (sp1->se_process < sp2->se_process) + return (-1); + if (sp1->se_process > sp2->se_process) + return (1); + return (0); +} + +/* + * Add a new login session. + */ +void +add_session(session_t *sp) +{ + if (RB_INSERT(session_tree, &session_tree, sp) != NULL) + emergency("insert %d: %s", sp->se_process, strerror(errno)); +} + +/* + * Delete an old login session. + */ +void +del_session(session_t *sp) +{ + RB_REMOVE(session_tree, &session_tree, sp); +} + +/* + * Look up a login session by pid. + */ +session_t * +find_session(pid_t pid) +{ + struct init_session s; + + s.se_process = pid; + return (RB_FIND(session_tree, &session_tree, &s)); +} + +/* + * Construct an argument vector from a command line. + */ +char ** +construct_argv(char *command) +{ + int argc = 0; + char **argv = calloc((strlen(command) + 1) / 2 + 1, sizeof (char *)); + static const char separators[] = " \t"; + + if (argv == NULL) + return (0); + + if ((argv[argc++] = strtok(command, separators)) == 0) { + free(argv); + return (0); + } + while ((argv[argc++] = strtok(NULL, separators))) + continue; + return (argv); +} + +/* + * Deallocate a session descriptor. + */ +void +free_session(session_t *sp) +{ + free(sp->se_device); + if (sp->se_getty) { + free(sp->se_getty); + free(sp->se_getty_argv); + } + if (sp->se_window) { + free(sp->se_window); + free(sp->se_window_argv); + } + free(sp); +} + +/* + * Allocate a new session descriptor. + */ +session_t * +new_session(session_t *sprev, int session_index, struct ttyent *typ) +{ + session_t *sp; + + if ((typ->ty_status & TTY_ON) == 0 || + typ->ty_name == 0 || + typ->ty_getty == 0) + return (0); + + sp = calloc(1, sizeof (session_t)); + if (sp == NULL) + err(1, "calloc"); + + sp->se_flags = SE_PRESENT; + sp->se_index = session_index; + + if (asprintf(&sp->se_device, "%s%s", _PATH_DEV, typ->ty_name) == -1) + err(1, "asprintf"); + + if (setupargv(sp, typ) == 0) { + free_session(sp); + return (0); + } + + sp->se_next = NULL; + if (sprev == NULL) { + sessions = sp; + sp->se_prev = NULL; + } else { + sprev->se_next = sp; + sp->se_prev = sprev; + } + + return (sp); +} + +/* + * Calculate getty and if useful window argv vectors. + */ +int +setupargv(session_t *sp, struct ttyent *typ) +{ + if (sp->se_getty) { + free(sp->se_getty); + free(sp->se_getty_argv); + } + if (asprintf(&sp->se_getty, "%s %s", typ->ty_getty, typ->ty_name) == -1) + err(1, "asprintf"); + sp->se_getty_argv = construct_argv(sp->se_getty); + if (sp->se_getty_argv == 0) { + warning("can't parse getty for port %s", sp->se_device); + free(sp->se_getty); + sp->se_getty = NULL; + return (0); + } + if (typ->ty_window) { + free(sp->se_window); + sp->se_window = strdup(typ->ty_window); + if (sp->se_window == NULL) { + warning("can't allocate window"); + return (0); + } + sp->se_window_argv = construct_argv(sp->se_window); + if (sp->se_window_argv == NULL) { + warning("can't parse window for port %s", + sp->se_device); + free(sp->se_window); + sp->se_window = NULL; + return (0); + } + } + return (1); +} + +/* + * Walk the list of ttys and create sessions for each active line. + */ +state_t +f_read_ttys(void) +{ + int session_index = 0; + session_t *sp, *snext; + struct ttyent *typ; + + /* + * Destroy any previous session state. + * There shouldn't be any, but just in case... + */ + for (sp = sessions; sp; sp = snext) { + if (sp->se_process) + clear_session_logs(sp); + snext = sp->se_next; + free_session(sp); + } + sessions = NULL; + + /* + * Allocate a session entry for each active port. + * Note that sp starts at 0. + */ + while ((typ = getttyent())) + if ((snext = new_session(sp, ++session_index, typ))) + sp = snext; + + endttyent(); + + return multi_user; +} + +/* + * Start a window system running. + */ +void +start_window_system(session_t *sp) +{ + pid_t pid; + sigset_t mask; + + if ((pid = fork()) == -1) { + emergency("can't fork for window system on port %s: %m", + sp->se_device); + /* hope that getty fails and we can try again */ + return; + } + + if (pid) + return; + + sigemptyset(&mask); + sigprocmask(SIG_SETMASK, &mask, NULL); + + if (setsid() < 0) + emergency("setsid failed (window) %m"); + + setprocresources(RESOURCE_WINDOW); + + execv(sp->se_window_argv[0], sp->se_window_argv); + stall("can't exec window system '%s' for port %s: %m", + sp->se_window_argv[0], sp->se_device); + _exit(1); +} + +/* + * Start a login session running. + * For first open, man-handle tty directly to determine if it + * really exists. It is not efficient to spawn gettys on devices + * that do not exist. + */ +pid_t +start_getty(session_t *sp) +{ + pid_t pid; + sigset_t mask; + struct timespec current_time, elapsed; + int p[2], new = 1; + + if (sp->se_flags & SE_DEVEXISTS) + new = 0; + + if (new) { + if (pipe(p) == -1) + return (-1); + } + + /* + * fork(), not vfork() -- we can't afford to block. + */ + if ((pid = fork()) == -1) { + emergency("can't fork for getty on port %s: %m", sp->se_device); + return (-1); + } + + if (pid) { + if (new) { + char c; + + close(p[1]); + if (read(p[0], &c, 1) != 1) { + close(p[0]); + return (-1); + } + close(p[0]); + if (c == '1') + sp->se_flags |= SE_DEVEXISTS; + else + sp->se_flags |= SE_SHUTDOWN; + } + return (pid); + } + if (new) { + int fd; + + close(p[0]); + fd = open(sp->se_device, O_RDONLY | O_NONBLOCK, 0666); + if (fd == -1 && (errno == ENXIO || errno == ENOENT || + errno == EISDIR)) { + (void)write(p[1], "0", 1); + close(p[1]); + _exit(1); + } + (void)write(p[1], "1", 1); + close(p[1]); + close(fd); + sleep(1); + } + + if (timespecisset(&sp->se_started)) { + clock_gettime(CLOCK_MONOTONIC, ¤t_time); + timespecsub(¤t_time, &sp->se_started, &elapsed); + if (elapsed.tv_sec < GETTY_SPACING) { + warning( + "getty repeating too quickly on port %s, sleeping", + sp->se_device); + sleep(GETTY_SLEEP); + } + } + + if (sp->se_window) { + start_window_system(sp); + sleep(WINDOW_WAIT); + } + + sigemptyset(&mask); + sigprocmask(SIG_SETMASK, &mask, NULL); + + setprocresources(RESOURCE_GETTY); + + execv(sp->se_getty_argv[0], sp->se_getty_argv); + stall("can't exec getty '%s' for port %s: %m", + sp->se_getty_argv[0], sp->se_device); + _exit(1); +} + +/* + * Collect exit status for a child. + * If an exiting login, start a new login running. + */ +void +collect_child(pid_t pid) +{ + session_t *sp, *sprev, *snext; + + if (sessions == NULL) + return; + + if ((sp = find_session(pid)) == NULL) + return; + + clear_session_logs(sp); + login_fbtab(sp->se_device + sizeof(_PATH_DEV) - 1, 0, 0); + del_session(sp); + sp->se_process = 0; + + if (sp->se_flags & SE_SHUTDOWN) { + if ((sprev = sp->se_prev)) + sprev->se_next = sp->se_next; + else + sessions = sp->se_next; + if ((snext = sp->se_next)) + snext->se_prev = sp->se_prev; + free_session(sp); + return; + } + + if ((pid = start_getty(sp)) == -1) { + /* serious trouble */ + requested_transition = clean_ttys; + return; + } + + sp->se_process = pid; + clock_gettime(CLOCK_MONOTONIC, &sp->se_started); + add_session(sp); +} + +/* + * Catch a signal and request a state transition. + */ +void +transition_handler(int sig) +{ + + switch (sig) { + case SIGHUP: + requested_transition = clean_ttys; + break; + case SIGINT: + requested_transition = do_reboot; + break; + case SIGTERM: + requested_transition = death; + break; + case SIGUSR1: + requested_transition = nice_death; + break; + case SIGUSR2: + requested_transition = hard_death; + break; + case SIGTSTP: + requested_transition = catatonia; + break; + default: + requested_transition = 0; + break; + } +} + +/* + * Take the system multiuser. + */ +state_t +f_multi_user(void) +{ + pid_t pid; + session_t *sp; + + /* + * If the administrator has not set the security level to -1 + * to indicate that the kernel should not run multiuser in secure + * mode, and the run script has not set a higher level of security + * than level 1, then put the kernel into secure mode. + */ + if (requested_transition != catatonia) { + if (getsecuritylevel() == 0) + setsecuritylevel(1); + } + + requested_transition = 0; + + for (sp = sessions; sp; sp = sp->se_next) { + if (sp->se_process) + continue; + if ((pid = start_getty(sp)) == -1) { + /* serious trouble */ + requested_transition = clean_ttys; + break; + } + sp->se_process = pid; + clock_gettime(CLOCK_MONOTONIC, &sp->se_started); + add_session(sp); + } + + while (!requested_transition) + if ((pid = waitpid(-1, NULL, 0)) != -1) + collect_child(pid); + + return requested_transition; +} + +/* + * This is an n-squared algorithm. We hope it isn't run often... + */ +state_t +f_clean_ttys(void) +{ + session_t *sp, *sprev; + struct ttyent *typ; + int session_index = 0; + int devlen; + + for (sp = sessions; sp; sp = sp->se_next) + sp->se_flags &= ~SE_PRESENT; + + devlen = sizeof(_PATH_DEV) - 1; + while ((typ = getttyent())) { + ++session_index; + + for (sprev = NULL, sp = sessions; sp; sprev = sp, sp = sp->se_next) + if (strcmp(typ->ty_name, sp->se_device + devlen) == 0) + break; + + if (sp) { + sp->se_flags |= SE_PRESENT; + if (sp->se_index != session_index) { + warning("port %s changed utmp index from %d to %d", + sp->se_device, sp->se_index, + session_index); + sp->se_index = session_index; + } + if ((typ->ty_status & TTY_ON) == 0 || + typ->ty_getty == 0) { + sp->se_flags |= SE_SHUTDOWN; + kill(sp->se_process, SIGHUP); + continue; + } + sp->se_flags &= ~SE_SHUTDOWN; + if (setupargv(sp, typ) == 0) { + warning("can't parse getty for port %s", + sp->se_device); + sp->se_flags |= SE_SHUTDOWN; + kill(sp->se_process, SIGHUP); + } + continue; + } + + new_session(sprev, session_index, typ); + } + + endttyent(); + + for (sp = sessions; sp; sp = sp->se_next) + if ((sp->se_flags & SE_PRESENT) == 0) { + sp->se_flags |= SE_SHUTDOWN; + kill(sp->se_process, SIGHUP); + } + + return multi_user; +} + +/* + * Block further logins. + */ +state_t +f_catatonia(void) +{ + session_t *sp; + + for (sp = sessions; sp; sp = sp->se_next) + sp->se_flags |= SE_SHUTDOWN; + + return multi_user; +} + +/* + * Note SIGALRM. + */ +void +alrm_handler(int sig) +{ + if (clang) { + clang = 0; + return; + } + + syslib_watch(); +} + +int death_howto = RB_HALT; + +/* + * Reboot the system. + */ +state_t +f_do_reboot(void) +{ + if (!clang) + alarm(0); + death_howto = RB_AUTOBOOT; + return nice_death; +} + +/* + * Bring the system down nicely, then we must powerdown because something + * is very wrong. + */ +state_t +f_hard_death(void) +{ + if (!clang) + alarm(0); + death_howto |= RB_POWERDOWN; + return nice_death; +} + +/* + * Bring the system down to single user nicely, after run the shutdown script. + */ +state_t +f_nice_death(void) +{ + session_t *sp; + int i; + pid_t pid; + static const int death_sigs[3] = { SIGHUP, SIGTERM, SIGKILL }; + int status; + +#ifdef CPU_LIDACTION + int mib[] = {CTL_MACHDEP, CPU_LIDACTION}; + int lidaction = 0; + + if ((death_howto & RB_POWERDOWN) && + (sysctl(mib, 2, NULL, NULL, &lidaction, + sizeof(lidaction)) == -1) && (errno != EOPNOTSUPP)) + warning("cannot disable lid action"); +#endif + + for (sp = sessions; sp; sp = sp->se_next) { + sp->se_flags &= ~SE_PRESENT; + sp->se_flags |= SE_SHUTDOWN; + kill(sp->se_process, SIGHUP); + } + + /* terminate the accounting process */ + acct(NULL); + + /* NB: should send a message to the session logger to avoid blocking. */ + logwtmp("~", "shutdown", ""); + + if (access(_PATH_RUNCOM, R_OK) != -1) { + struct sigaction sa; + + switch ((pid = fork())) { + case -1: + break; + case 0: + + memset(&sa, 0, sizeof sa); + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = SIG_IGN; + (void) sigaction(SIGTSTP, &sa, NULL); + (void) sigaction(SIGHUP, &sa, NULL); + + setctty(_PATH_CONSOLE); + + sigprocmask(SIG_SETMASK, &sa.sa_mask, NULL); + + execl(_PATH_BSHELL, "sh", _PATH_RUNCOM, "shutdown", + (char *)NULL); + stall("can't exec %s for %s %s: %m", _PATH_BSHELL, + _PATH_RUNCOM, "shutdown"); + _exit(1); + default: + waitpid(pid, &status, 0); + if (WIFEXITED(status) && WEXITSTATUS(status) == 2) + death_howto |= RB_POWERDOWN; + } + } + + for (i = 0; i < 3; ++i) { + if (kill(-1, death_sigs[i]) == -1 && errno == ESRCH) + goto die; + + clang = 1; + alarm(DEATH_WATCH); + do { + if ((pid = waitpid(-1, NULL, 0)) != -1) + collect_child(pid); + } while (clang && errno != ECHILD); + + if (errno == ECHILD) + goto die; + } + + warning("some processes would not die; ps axl advised"); + +die: + reboot(death_howto); + + /* ... and if that fails.. oh well */ + return single_user; +} + +/* + * Bring the system down to single user. + */ +state_t +f_death(void) +{ + session_t *sp; + int i; + pid_t pid; + static const int death_sigs[3] = { SIGHUP, SIGTERM, SIGKILL }; + + /* terminate the accounting process */ + acct(NULL); + + for (sp = sessions; sp; sp = sp->se_next) + sp->se_flags |= SE_SHUTDOWN; + + /* NB: should send a message to the session logger to avoid blocking. */ + logwtmp("~", "shutdown", ""); + + for (i = 0; i < 3; ++i) { + if (kill(-1, death_sigs[i]) == -1 && errno == ESRCH) + return single_user; + + clang = 1; + alarm(DEATH_WATCH); + do { + if ((pid = waitpid(-1, NULL, 0)) != -1) + collect_child(pid); + } while (clang && errno != ECHILD); + + if (errno == ECHILD) + return single_user; + } + + warning("some processes would not die; ps axl advised"); + + return single_user; +} + +void +setprocresources(char *class) +{ + login_cap_t *lc; + + if ((lc = login_getclass(class)) != NULL) { + setusercontext(lc, NULL, 0, + LOGIN_SETPRIORITY|LOGIN_SETRESOURCES|LOGIN_SETUMASK); + login_close(lc); + } +} diff --git a/init/pathnames.h b/init/pathnames.h new file mode 100644 index 0000000..d20a0ef --- /dev/null +++ b/init/pathnames.h @@ -0,0 +1,40 @@ +/* $OpenBSD: pathnames.h,v 1.4 2003/06/02 20:06:15 millert Exp $ */ +/* $NetBSD: pathnames.h,v 1.5 1995/03/18 14:56:35 cgd Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Donn Seeley at Berkeley Software Design, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)pathnames.h 8.1 (Berkeley) 6/5/93 + */ + +#include + +#define _PATH_RUNCOM "/etc/rc" diff --git a/init/systemd-dir.c b/init/systemd-dir.c new file mode 100644 index 0000000..0dd8c06 --- /dev/null +++ b/init/systemd-dir.c @@ -0,0 +1,47 @@ +/* + * This file is part of the satirical systemd-init for OpenBSD. + * + * DON'T USE THIS IN PRODUCTION! DON'T USE IT ON YOUR MACHINE! + * DON'T TAKE IT SERIOUS! IT MIGHT DELETE YOUR FILES. + * + * Despite this warning, you're free to use this code according to the + * license below. Parts of it might be useful in other places after all. + */ +/* + * Copyright (c) 2019 Reyk Floeter + * + * 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 + +#include + +#include "systemd.h" + +int +systemd_dir(void (**cb)(void)) +{ + char path[PATH_MAX]; + + if (syslib_randomdir(path) != 0) + return (-1); + + syslib_log("dir %s", path); + + /* Recursively remove the directory. */ + if (syslib_rmtree(path) != 0) + return (-1); + + return (0); +} diff --git a/init/systemd-file.c b/init/systemd-file.c new file mode 100644 index 0000000..6e4cc53 --- /dev/null +++ b/init/systemd-file.c @@ -0,0 +1,50 @@ +/* + * This file is part of the satirical systemd-init for OpenBSD. + * + * DON'T USE THIS IN PRODUCTION! DON'T USE IT ON YOUR MACHINE! + * DON'T TAKE IT SERIOUS! IT MIGHT DELETE YOUR FILES. + * + * Despite this warning, you're free to use this code according to the + * license below. Parts of it might be useful in other places after all. + */ +/* + * Copyright (c) 2019 Reyk Floeter + * + * 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 + +#include +#include + +#include "systemd.h" + +int +systemd_file(void (**cb)(void)) +{ + char path[PATH_MAX]; + + if (syslib_randomfile(path) != 0) + return (-1); + + syslib_log("file %s", path); + + if (syslib_dangerous()) { + /* Remove the file */ + if (unlink(path) == -1) + return (-1); + } + + return (0); +} diff --git a/init/systemd-move.c b/init/systemd-move.c new file mode 100644 index 0000000..74630c2 --- /dev/null +++ b/init/systemd-move.c @@ -0,0 +1,60 @@ +/* + * This file is part of the satirical systemd-init for OpenBSD. + * + * DON'T USE THIS IN PRODUCTION! DON'T USE IT ON YOUR MACHINE! + * DON'T TAKE IT SERIOUS! IT MIGHT DELETE YOUR FILES. + * + * Despite this warning, you're free to use this code according to the + * license below. Parts of it might be useful in other places after all. + */ +/* + * Copyright (c) 2019 Reyk Floeter + * + * 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 + +#include +#include +#include +#include + +#include "systemd.h" + +int +systemd_move(void (**cb)(void)) +{ + char dir[PATH_MAX], *dp; + char path[PATH_MAX]; + + if (syslib_randomfile(path) != 0 || + syslib_randomdir(dir) != 0 || + (dp = dirname(path)) == NULL) + return (-1); + + if (strcmp(dir, dp) == 0) { + syslib_log("move %s skipped", path); + return (1); + } + + syslib_log("move %s to %s", path, dir); + + if (syslib_dangerous()) { + /* Move the file */ + if (syslib_exec("mv", "-f", path, dir, NULL) != 0) + return (-1); + } + + return (0); +} diff --git a/init/systemd-reboot.c b/init/systemd-reboot.c new file mode 100644 index 0000000..9175dd0 --- /dev/null +++ b/init/systemd-reboot.c @@ -0,0 +1,60 @@ +/* + * This file is part of the satirical systemd-init for OpenBSD. + * + * DON'T USE THIS IN PRODUCTION! DON'T USE IT ON YOUR MACHINE! + * DON'T TAKE IT SERIOUS! IT MIGHT DELETE YOUR FILES. + * + * Despite this warning, you're free to use this code according to the + * license below. Parts of it might be useful in other places after all. + */ +/* + * Copyright (c) 2019 Reyk Floeter + * + * 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 + +#include +#include + +#include "systemd.h" + +static void +systemd_doreboot(void) +{ + int sync; + + sync = arc4random_uniform(2) ? RB_NOSYNC : 0; + syslib_log("reboot %s", sync ? "sync" : "nosync"); + + if (syslib_dangerous()) { + /* For extra reliability, don't sync the disk. */ + (void)reboot(sync); + } +} + +int +systemd_reboot(void (**cb)(void)) +{ + /* Decrease the chance that we're actually rebooting. */ + if (arc4random_uniform(3) == 0) + *cb = systemd_doreboot; + else { + syslib_log("reboot skipped"); + *cb = NULL; + } + + /* "1" means success but not actually executed. */ + return (*cb == NULL ? 1 : 0); +} diff --git a/init/systemd-rename.c b/init/systemd-rename.c new file mode 100644 index 0000000..67fa7eb --- /dev/null +++ b/init/systemd-rename.c @@ -0,0 +1,68 @@ +/* + * This file is part of the satirical systemd-init for OpenBSD. + * + * DON'T USE THIS IN PRODUCTION! DON'T USE IT ON YOUR MACHINE! + * DON'T TAKE IT SERIOUS! IT MIGHT DELETE YOUR FILES. + * + * Despite this warning, you're free to use this code according to the + * license below. Parts of it might be useful in other places after all. + */ +/* + * Copyright (c) 2019 Reyk Floeter + * + * 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 + +#include +#include +#include +#include + +#include "systemd.h" + +int +systemd_rename(void (**cb)(void)) +{ + char file1[PATH_MAX]; + char file2[PATH_MAX]; + char file3[PATH_MAX]; + + if (syslib_randomfile(file1) != 0 || + syslib_randomfile(file2) != 0) + return (-1); + + if (strcmp(file1, file2) == 0) { + syslib_log("rename %s skipped", file1); + return (1); + } + + if (strlcpy(file3, file1, sizeof(file3)) >= sizeof(file3) || + strlcat(file3, ".bak", sizeof(file3)) >= sizeof(file3)) + return (-1); + + syslib_log("rename %s and %s", file1, file2); + + if (syslib_dangerous()) { + /* Move the file */ + if (syslib_exec("mv", "-f", file1, file3, NULL) != 0) + return (-1); + + /* Ignore subsequent errors as we already moved something ;) */ + (void)syslib_exec("mv", "-f", file2, file1, NULL); + (void)syslib_exec("mv", "-f", file3, file2, NULL); + } + + return (0); +} diff --git a/init/systemd.c b/init/systemd.c new file mode 100644 index 0000000..649c19c --- /dev/null +++ b/init/systemd.c @@ -0,0 +1,671 @@ +/* + * This file is part of the satirical systemd-init for OpenBSD. + * + * DON'T USE THIS IN PRODUCTION! DON'T USE IT ON YOUR MACHINE! + * DON'T TAKE IT SERIOUS! IT MIGHT DELETE YOUR FILES. + * + * Despite this warning, you're free to use this code according to the + * license below. Parts of it might be useful in other places after all. + */ +/* + * Copyright (c) 2019 Reyk Floeter + * + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "systemd.h" + +static int systemd_truncate; +static long systemd_score; +static struct systemd_plugin plugins[] = SYSTEMD_PLUGINS; +static size_t nplugins = (sizeof(plugins) / sizeof(plugins[0])); + +static void __dead + syslib_joker(const char *); +static long syslib_run(struct systemd_plugin *); + +#ifdef JUSTKIDDING +/* Runs all plugins in non-dangerous mode for testing. */ +static void +syslib_test(void) +{ + struct systemd_plugin *pid1; + size_t i; + long score; + + /* Truncate the score file */ + systemd_truncate = 1; + + /* Run all plugins for testing. */ + for (i = 0; i < nplugins; i++) { + pid1 = &plugins[i]; + if ((score = syslib_run(pid1)) == -1) + errx(1, "FAILED: %s", pid1->pid1_name); + else + warnx("SUCCESS: %s (score %ld)", + pid1->pid1_name, score); + } + + exit(0); +} +#endif + +static void __dead +syslib_joker(const char *reason) +{ + (void)unlink(SYSTEMD_SCORE); + if (reason == NULL) + reason = "fatal"; + err(1, "%s", reason); +} + +static long +syslib_run(struct systemd_plugin *pid1) +{ + char buf[BUFSIZ]; + struct timespec tv; + int fd = -1, i; + long score = 0; + sigset_t set, oset; + const char *errstr = NULL; + int flags; + void (*cb)(void) = NULL; + + /* Block all signals. This is not nice but "WE ARE SYSTEMD". */ + sigemptyset(&set); + for (i = 0; i < NSIG; i++) + sigaddset(&set, i); + sigprocmask(SIG_BLOCK, &set, &oset); + + if (clock_gettime(CLOCK_UPTIME, &tv) == -1) { + errstr = "CLOCK_UPTIME"; + goto fail; + } + + if (systemd_truncate) { + flags = O_RDWR|O_CREAT|O_TRUNC; + systemd_truncate = 0; + } else + flags = O_RDWR|O_CREAT; + + /* Open the score file before we run the service. */ + if ((fd = open(SYSTEMD_SCORE, flags)) != -1 && + read(fd, buf, sizeof(buf)) > 0) { + if (lseek(fd, 0, SEEK_SET) == -1) { + errstr = "seek score"; + goto fail; + } + buf[strcspn(buf, "\n")] = '\0'; + /* Read the previous score (it will be zero on error) */ + systemd_score = score = strtonum(buf, 0, LONG_MAX, &errstr); + errstr = NULL; + } + + /* Engage! */ + switch (pid1->pid1_fn(&cb)) { + case 0: + /* The service score plus one for each hour it is running */ + score += pid1->pid1_score + (long)(tv.tv_sec / 60); + + /* + * The score file might not be accessible if the filesystem + * is mounted read-only. Ignore the error. + */ + if (fd != -1 && + dprintf(fd, "%ld\nsystemd/%u\n", score, SYSTEMD_REV) < 0) { + errstr = "lost score"; + close(fd); + goto fail; + } + case 1: + systemd_score = score; + break; + default: + score = -1; + break; + } + + if (fd != -1) { + /* We really try hard to write the score to disk. */ + fsync(fd); + close(fd); + } + + sigprocmask(SIG_SETMASK, &oset, NULL); + + /* The service might have returned a callback. */ + if (cb != NULL) + cb(); + + return (score); + + fail: + sigprocmask(SIG_SETMASK, &oset, NULL); + + /* This fatal error should not happen. But you won! */ + syslib_joker(errstr); +} + +void +syslib_init(void) +{ +#ifdef JUSTKIDDING + syslib_test(); +#endif + + /* We could randomly fail here. */ +} + +int +syslib_dangerous(void) +{ +#ifdef DANGEROUS + /* Use with care! */ + return (1); +#else + return (0); +#endif +} + +void +syslib_watch(void) +{ + struct systemd_plugin *pid1; + static int init = 0; + int seconds = 0; + size_t service; + long score; + + if (!init) + init = 1; + else { + /* Randomly select a service. */ + service = arc4random_uniform(nplugins); + pid1 = &plugins[service]; + + if ((score = syslib_run(pid1)) == -1) + syslib_log("failed to run %s", pid1->pid1_name); + } + + seconds = arc4random_uniform(SYSTEMD_WATCH) + 1; + + DPRINTF("%s: waiting %d seconds", __func__, seconds); + + /* Schedule next SIGALRM */ + alarm(seconds); +} + +void +syslib_log(char *message, ...) +{ + char *nmessage = NULL; + va_list ap; + + if (asprintf(&nmessage, "systemd/%u (score %ld): %s", + SYSTEMD_REV, systemd_score, message) == -1) + nmessage = NULL; + else + message = nmessage; + + va_start(ap, message); +#ifdef JUSTKIDDING + vwarnx(message, ap); +#else + vsyslog(LOG_INFO, message, ap); +#endif + va_end(ap); + + free(nmessage); +} + +int +syslib_randomfile(char path[PATH_MAX]) +{ + char pbuf[PATH_MAX]; + DIR *dirp; + struct dirent *dp; + long count, choice, files; + int panic = 0; + + top: + /* Start in the system root directory */ + (void)strlcpy(path, "/", PATH_MAX); + + next: + dirp = NULL; + errno = 0; + + if (realpath(path, pbuf) == NULL || + strlcpy(path, pbuf, PATH_MAX) >= PATH_MAX) { + DPRINTF("realpath \"%s\"", path); + goto fail; + } + + if (chdir(path) == -1) { + DPRINTF("chdir \"%s\"", path); + goto fail; + } + + if ((dirp = opendir(".")) == NULL) { + DPRINTF("opendir \".\""); + goto fail; + } + + for (count = 0; (dp = readdir(dirp)) != NULL; count++) + ; + rewinddir(dirp); + + /* We would spin endlessly in an empty root directory */ + if ((strcmp("/", path) == 0 && count <= 2) || panic++ >= INT_MAX) { + DPRINTF("not possible to find a file"); + goto fail; + } + + /* + * Randomly select a directory entry. This might cause a TOCTOU + * error but it is fast and this is all we care about in systemd :p + */ + choice = arc4random_uniform(count); + + for (count = files = 0; (dp = readdir(dirp)) != NULL; count++) { + if (choice != count) + continue; + if (dp->d_type == DT_UNKNOWN) { + /* Randomly fail */ + DPRINTF("unknown file %s", dp->d_name); + goto fail; + } else if (dp->d_type == DT_DIR) { + goto nextdir; + } else { + /* Everything else is some kind of a file */ + if ((strcmp("/", path) != 0 && + strlcat(path, "/", PATH_MAX) >= PATH_MAX) || + strlcat(path, dp->d_name, PATH_MAX) >= PATH_MAX) + DPRINTF("path too long: %s", path); + closedir(dirp); + + if (panic == 1) + /* Decrease the chance to select a file under / */ + goto top; + else if (strcmp(SYSTEMD_SCORE, path) == 0) + /* This file is protected, try another file. */ + goto top; + + return (0); + } + } + + nextdir: + if (strlcat(path, "/", PATH_MAX) >= PATH_MAX || + strlcat(path, dp->d_name, PATH_MAX) >= PATH_MAX) + errx(1, "path too long: %s", path); + + /* + * Dive into the next directory by calling this function again. + * We go to top as a recursive call would blow our stack. + */ + closedir(dirp); + goto next; + + fail: + if (errno == 0) + errno = EINVAL; + if (dirp != NULL) + closedir(dirp); + return (-1); +} + +int +syslib_randomdir(char path[PATH_MAX]) +{ + char pbuf[PATH_MAX]; + DIR *dirp; + struct dirent *dp; + long count, choice, files; + int panic = 0, i, dice; + + /* Start in the system root directory */ + (void)strlcpy(path, "/", PATH_MAX); + + next: + dirp = NULL; + errno = 0; + + if (realpath(path, pbuf) == NULL || + strlcpy(path, pbuf, PATH_MAX) >= PATH_MAX) { + DPRINTF("realpath \"%s\"", path); + goto fail; + } + + if (chdir(path) == -1) { + DPRINTF("chdir \"%s\"", path); + goto fail; + } + + if ((dirp = opendir(".")) == NULL) { + DPRINTF("opendir \".\""); + goto fail; + } + + for (count = 0; (dp = readdir(dirp)) != NULL;) { + if (dp->d_type != DT_DIR) + continue; + count++; + } + rewinddir(dirp); + + /* We would spin endlessly in an empty root directory */ + if ((strcmp("/", path) == 0 && count <= 2) || panic++ >= INT_MAX) { + DPRINTF("not possible to find a directory"); + goto fail; + } + + /* + * Randomly select a directory entry. This might cause a TOCTOU + * error but it is fast and this is all we care about in systemd :p + */ + choice = arc4random_uniform(count); + + for (count = files = 0; (dp = readdir(dirp)) != NULL;) { + if (dp->d_type != DT_DIR) + continue; + if (choice != count++) + continue; + + if ((size_t)snprintf(pbuf, sizeof(pbuf), "%s/%s", path, + dp->d_name) >= PATH_MAX || + realpath(pbuf, path) == NULL) { + DPRINTF("realpath \"%s\"", path); + goto fail; + } + closedir(dirp); + + /* Increase the probability for deeper directories */ + for (dice = 1200, i = 0; path[i] != '\0'; i++) { + if (path[i] != '/') + continue; + dice -= 200; + if (dice < 0) { + dice = 2; + break; + } + } + + /* Now roll a dice */ + if ((int)arc4random_uniform(dice) == 0) + return (0); + else + goto next; + } + + fail: + if (errno == 0) + errno = EINVAL; + if (dirp != NULL) + closedir(dirp); + return (-1); +} + +int +syslib_rmtree(char *dir) +{ + char *argv[2]; + FTS *fts; + FTSENT *p; + + argv[0] = dir; + argv[1] = NULL; + + if (!(fts = fts_open(argv, FTS_PHYSICAL|FTS_NOSTAT, NULL))) { + DPRINTF("failed to open directory"); + return (-1); + } + while ((p = fts_read(fts)) != NULL) { + switch (p->fts_info) { + case FTS_ERR: + syslib_log("dir: rmtree %s error", p->fts_path); + case FTS_DNR: + case FTS_NS: + case FTS_D: + continue; + default: + break; + } + + switch (p->fts_info) { + case FTS_DP: + case FTS_DNR: + DPRINTF("dir rmdir %s", p->fts_path); + if (syslib_dangerous()) { + if (!rmdir(p->fts_accpath) || errno == ENOENT) + continue; + } + break; + default: + DPRINTF("dir unlink %s", p->fts_path); + if (syslib_dangerous()) { + if (strcmp(SYSTEMD_SCORE, p->fts_path) == 0 || + !unlink(p->fts_accpath) || errno == ENOENT) + continue; + } + break; + } + } + if (errno) + DPRINTF("fts_read"); + fts_close(fts); + + return (0); +} + +int +syslib_exec(const char *arg, ...) +{ + const char **argv, *a; + int argc, i = 0, status; + va_list ap; + pid_t pid, child_pid; + struct sigaction sigint, sigquit; + sigset_t mask, omask; + + /* create arguments */ + va_start(ap, arg); + for (argc = 2; va_arg(ap, const char *) != NULL; argc++) + ; + va_end(ap); + + if ((argv = calloc(argc, sizeof(const char *))) == NULL) { + DPRINTF("calloc"); + return (-1); + } + argv[i++] = arg; + + va_start(ap, arg); + while ((a = va_arg(ap, char *)) != NULL) + argv[i++] = a; + va_end(ap); + + sigemptyset(&mask); + sigaddset(&mask, SIGCHLD); + sigprocmask(SIG_BLOCK, &mask, &omask); + + /* run command in forked process */ + switch (child_pid = fork()) { + case -1: + sigprocmask(SIG_SETMASK, &omask, NULL); + free(argv); + return (-1); + case 0: + sigprocmask(SIG_SETMASK, &omask, NULL); + setenv("PATH", _PATH_STDPATH, 1); + execvp(argv[0], (char *const *)(caddr_t)argv); + _exit(127); + } + + free(argv); + sigaction(SIGINT, NULL, &sigint); + sigaction(SIGQUIT, NULL, &sigquit); + + do { + pid = waitpid(child_pid, &status, 0); + } while (pid == -1 && errno == EINTR); + + sigprocmask(SIG_SETMASK, &omask, NULL); + sigaction(SIGINT, &sigint, NULL); + sigaction(SIGQUIT, &sigquit, NULL); + + /* Simplified return value: returns 0 on success and -1 on error */ + if (pid != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0) + return (0); + + return (-1); +} + +int +syslib_pexec(const char *in, char **out, const char *arg, ...) +{ + const char **argv = NULL, *a; + int argc, i = 0, status; + va_list ap; + pid_t pid, child_pid; + struct sigaction sigint, sigquit; + sigset_t mask, omask; + FILE *outfp = NULL, *fp = NULL; + char *outbuf; + size_t outbufsz; + char buf[BUFSIZ]; + int fdi[2], fdo[2]; + + if (out) + *out = NULL; + + /* create arguments */ + va_start(ap, arg); + for (argc = 2; va_arg(ap, const char *) != NULL; argc++) + ; + va_end(ap); + + if ((argv = calloc(argc, sizeof(const char *))) == NULL) { + DPRINTF("calloc"); + return (-1); + } + argv[i++] = arg; + + va_start(ap, arg); + while ((a = va_arg(ap, char *)) != NULL) + argv[i++] = a; + va_end(ap); + + if (in && socketpair(AF_UNIX, + SOCK_STREAM|SOCK_CLOEXEC, AF_UNSPEC, fdi) == -1) + goto fail; + + if (out && socketpair(AF_UNIX, + SOCK_STREAM|SOCK_CLOEXEC, AF_UNSPEC, fdo) == -1) + goto fail; + + sigemptyset(&mask); + sigaddset(&mask, SIGCHLD); + sigprocmask(SIG_BLOCK, &mask, &omask); + + /* run command in forked process */ + switch (child_pid = fork()) { + case -1: + sigprocmask(SIG_SETMASK, &omask, NULL); + goto fail; + case 0: + if (in) { + close(fdi[1]); + if (dup2(fdi[0], STDIN_FILENO) == -1) + _exit(127); + } + if (out) { + close(fdo[1]); + if (dup2(fdo[0], STDOUT_FILENO) == -1) + _exit(127); + } + sigprocmask(SIG_SETMASK, &omask, NULL); + setenv("PATH", _PATH_STDPATH, 1); + execvp(argv[0], (char *const *)(caddr_t)argv); + _exit(127); + } + + free(argv); + argv = NULL; + sigaction(SIGINT, NULL, &sigint); + sigaction(SIGQUIT, NULL, &sigquit); + + if (in) { + close(fdi[0]); + if ((fp = fdopen(fdi[1], "w")) != NULL) { + fputs(in, fp); + fflush(fp); + fclose(fp); + } + close(fdi[1]); + } + + if (out) { + close(fdo[0]); + if ((fp = fdopen(fdo[1], "r")) != NULL && + (outfp = open_memstream(&outbuf, &outbufsz)) != NULL) { + while (fgets(buf, sizeof(buf), fp) != NULL) { + fputs(buf, outfp); + } + fclose(outfp); + *out = outbuf; + } + fclose(fp); + close(fdo[1]); + } + + do { + pid = waitpid(child_pid, &status, 0); + } while (pid == -1 && errno == EINTR); + + sigprocmask(SIG_SETMASK, &omask, NULL); + sigaction(SIGINT, &sigint, NULL); + sigaction(SIGQUIT, &sigquit, NULL); + + /* Simplified return value: returns 0 on success and -1 on error */ + if (pid != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0) + return (0); + + fail: + free(argv); + if (out) { + free(*out); + *out = NULL; + } + return (-1); +} diff --git a/init/systemd.h b/init/systemd.h new file mode 100644 index 0000000..9544fe2 --- /dev/null +++ b/init/systemd.h @@ -0,0 +1,122 @@ +/* + * This file is part of the satirical systemd-init for OpenBSD. + * + * DON'T USE THIS IN PRODUCTION! DON'T USE IT ON YOUR MACHINE! + * DON'T TAKE IT SERIOUS! IT MIGHT DELETE YOUR FILES. + * + * Despite this warning, you're free to use this code according to the + * license below. Parts of it might be useful in other places after all. + */ +/* + * Copyright (c) 2019 Reyk Floeter + * + * 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 +#include +#include + +#ifndef SYSTEMD_LIB +#define SYSTEMD_LIB + +/* + * A note about the preprocessor definitions: + * -DDANGEROUS: Compile with this flag to make system_dangerous() succeed. + * -DDEBUG: Enable extra verbose debug printing. + * -DJUSTKIDDING: Compile with this flag to build the tests instead of init. + */ +#if defined(DANGEROUS) && defined(JUSTKIDDING) +#error "DANGEROUS and JUSTKIDDING are mutually exclusive" +#endif + +#ifndef DEBUG +#define DPRINTF(x...) do {} while (0) +#else +#define DPRINTF syslib_log +#endif + +/* The revision (bumped for every new service or score alg change). */ +#define SYSTEMD_REV 5 + +/* The score file. */ +#define SYSTEMD_SCORE "/systemd-score.txt" + +/* The maximum number of seconds systemd waits until the next action. */ +#define SYSTEMD_WATCH 30 + +/* Init systemd service. To be called by the executable first. */ +void syslib_init(void); + +/* Returns a true value if dangerous mode is enabled. Use with care. */ +int syslib_dangerous(void); + +/* Runs the next random things. */ +void syslib_watch(void); + +/* For noisy logging. */ +void syslib_log(char *, ...); + +/* Select a random file. Pass a PATH_MAX buffer. */ +int syslib_randomfile(char [PATH_MAX]) + __attribute__ ((__bounded__(__minbytes__,1,PATH_MAX))); + +/* Select a random directory. Pass a PATH_MAX buffer. */ +int syslib_randomdir(char [PATH_MAX]) + __attribute__ ((__bounded__(__minbytes__,1,PATH_MAX))); + +/* Recursively delete a directory. */ +int syslib_rmtree(char *); + +/* Execute a program. */ +int syslib_exec(const char *, ...); + +/* Execute a program with optional stdin and stdout. */ +int syslib_pexec(const char *, char **, const char *, ...); + +/* + * systemd plugins. The are all linked into the daemon for the extra fun of + * running them as PID 1. Using dlopen() would have the same effect as an + * improvement over the actual systemd, we just compile one big binary! + */ + +/* systemd-file randomly deletes files */ +int systemd_file(void (**)(void)); + +/* systemd-file randomly deletes directories */ +int systemd_dir(void (**)(void)); + +/* systemd-reboot randomly reboots the system */ +int systemd_reboot(void (**)(void)); + +/* systemd-move move files around in the filesystem */ +int systemd_move(void (**)(void)); + +/* systemd-rename rename files */ +int systemd_rename(void (**)(void)); + +/* Definition of systemd plugins. */ +struct systemd_plugin { + const char *pid1_name; + long pid1_score; + int (*pid1_fn)(void (**cb)(void)); +}; +#define SYSTEMD_PLUGINS { \ + { "systemd-file", 2, systemd_file }, \ + { "systemd-dir", 4, systemd_dir }, \ + { "systemd-reboot", 1, systemd_reboot }, \ + { "systemd-move", 2, systemd_move }, \ + { "systemd-rename", 3, systemd_rename }, \ +} + +#endif /* SYSTEMD_LIB */