diff options
-rw-r--r-- | Makefile | 49 | ||||
-rw-r--r-- | molly-guard.xml | 344 | ||||
-rw-r--r-- | rc | 19 | ||||
-rwxr-xr-x | run.d/10-print-message | 22 | ||||
-rwxr-xr-x | run.d/30-query-hostname | 102 | ||||
-rwxr-xr-x | shutdown.in | 152 |
6 files changed, 688 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..afb034d --- /dev/null +++ b/Makefile @@ -0,0 +1,49 @@ +PREFIX?=/usr +cfgdir?=/etc/molly-guard +libdir?=$(PREFIX)/lib +sbindir?=$(PREFIX)/sbin +REALPATH?=$(libdir)/molly-guard + +all: molly-guard.8 shutdown + +%.8: DB2MAN=/usr/share/sgml/docbook/stylesheet/xsl/nwalsh/manpages/docbook.xsl +%.8: XP=xsltproc -''-nonet +%.8: %.xml + $(XP) $(DB2MAN) $< + +man: molly-guard.8 + man -l $< +.PHONY: man + +clean: + rm -f shutdown + rm -f molly-guard.8 +.PHONY: clean + +shutdown: shutdown.in + sed -e 's,@cfgdir@,$(cfgdir),g;s,@REALPATH@,$(REALPATH),g' $< > $@ + +install: shutdown molly-guard.8 + mkdir -m755 --parent $(DESTDIR)$(libdir)/molly-guard + install -m755 -oroot -oroot shutdown $(DESTDIR)$(libdir)/molly-guard/molly-guard + + mkdir -m755 --parent $(DESTDIR)$(sbindir) + ln -s $(libdir)/molly-guard/molly-guard $(DESTDIR)$(sbindir)/poweroff + ln -s $(libdir)/molly-guard/molly-guard $(DESTDIR)$(sbindir)/halt + ln -s $(libdir)/molly-guard/molly-guard $(DESTDIR)$(sbindir)/reboot + ln -s $(libdir)/molly-guard/molly-guard $(DESTDIR)$(sbindir)/shutdown + ln -s $(libdir)/molly-guard/molly-guard $(DESTDIR)$(sbindir)/coldreboot + ln -s $(libdir)/molly-guard/molly-guard $(DESTDIR)$(sbindir)/pm-hibernate + ln -s $(libdir)/molly-guard/molly-guard $(DESTDIR)$(sbindir)/pm-suspend + ln -s $(libdir)/molly-guard/molly-guard $(DESTDIR)$(sbindir)/pm-suspend-hybrid + + mkdir -m755 --parent $(DESTDIR)$(cfgdir) + install -m644 -oroot -oroot rc $(DESTDIR)$(cfgdir) + cp -r run.d $(DESTDIR)$(cfgdir) \ + && chown root.root $(DESTDIR)$(cfgdir)/run.d && chmod 755 $(DESTDIR)$(cfgdir)/run.d + + mkdir -m755 --parent $(DESTDIR)$(cfgdir)/messages.d + + mkdir -m755 --parent $(DESTDIR)$(PREFIX)/share/man/man8 + install -m644 -oroot -groot molly-guard.8 $(DESTDIR)$(PREFIX)/share/man/man8 +.PHONY: install diff --git a/molly-guard.xml b/molly-guard.xml new file mode 100644 index 0000000..9c01f2f --- /dev/null +++ b/molly-guard.xml @@ -0,0 +1,344 @@ +<?xml version='1.0' encoding='ISO-8859-1'?> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" +"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd" [ + +<!-- + +Process this file with an XSLT processor: `xsltproc \ +-''-nonet /usr/share/sgml/docbook/stylesheet/xsl/nwalsh/\ +manpages/docbook.xsl manpage.dbk'. A manual page +<package>.<section> will be generated. You may view the +manual page with: nroff -man <package>.<section> | less'. A +typical entry in a Makefile or Makefile.am is: + +DB2MAN=/usr/share/sgml/docbook/stylesheet/xsl/nwalsh/\ +manpages/docbook.xsl +XP=xsltproc -''-nonet + +manpage.1: manpage.dbk + $(XP) $(DB2MAN) $< + +The xsltproc binary is found in the xsltproc package. The +XSL files are in docbook-xsl. Please remember that if you +create the nroff version in one of the debian/rules file +targets (such as build), you will need to include xsltproc +and docbook-xsl in your Build-Depends control field. + +--> + + <!-- Fill in your name for FIRSTNAME and SURNAME. --> + <!ENTITY dhfirstname "<firstname>martin f.</firstname>"> + <!ENTITY dhsurname "<surname>krafft</surname>"> + <!-- Please adjust the date whenever revising the manpage. --> + <!ENTITY dhdate "<date>Apr 19, 2008</date>"> + <!-- SECTION should be 1-8, maybe w/ subsection other parameters are + allowed: see man(7), man(1). --> + <!ENTITY dhsection "<manvolnum>8</manvolnum>"> + <!ENTITY dhemail "<email>madduck@madduck.net</email>"> + <!ENTITY dhusername "martin f. krafft"> + <!ENTITY dhucpackage "<refentrytitle>molly-guard</refentrytitle>"> + <!ENTITY dhpackage "molly-guard"> + <!ENTITY dhcommand "<command>molly-guard</command>"> + + <!ENTITY debian "<productname>Debian</productname>"> + <!ENTITY gnu "<acronym>GNU</acronym>"> + <!ENTITY gpl "&gnu; <acronym>GPL</acronym>"> +]> + +<refentry> + <refentryinfo> + <address> + &dhemail; + </address> + <copyright> + <year>2008</year> + <holder>&dhusername;</holder> + </copyright> + &dhdate; + </refentryinfo> + <refmeta> + &dhucpackage; + + &dhsection; + </refmeta> + <refnamediv> + <refname>&dhcommand;</refname> + + <refpurpose>guard against accidental shutdowns/reboots</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <cmdsynopsis> + <command>shutdown</command> + <arg choice="opt"> + -<option>hV</option> + </arg> + <arg choice="opt"> + <option>--molly-guard-do-nothing</option> + </arg> + <arg choice="opt"> + -- <replaceable>script_options</replaceable> + </arg> + </cmdsynopsis> + <cmdsynopsis> + <command>halt</command> + <arg choice="opt"> + -<option>hV</option> + </arg> + <arg choice="opt"> + <option>--molly-guard-do-nothing</option> + </arg> + <arg choice="opt"> + -- <replaceable>script_options</replaceable> + </arg> + </cmdsynopsis> + <cmdsynopsis> + <command>reboot</command> + <arg choice="opt"> + -<option>hV</option> + </arg> + <arg choice="opt"> + <option>--molly-guard-do-nothing</option> + </arg> + <arg choice="opt"> + -- <replaceable>script_options</replaceable> + </arg> + </cmdsynopsis> + <cmdsynopsis> + <command>poweroff</command> + <arg choice="opt"> + -<option>hV</option> + </arg> + <arg choice="opt"> + <option>--molly-guard-do-nothing</option> + </arg> + <arg choice="opt"> + -- <replaceable>script_options</replaceable> + </arg> + </cmdsynopsis> + <cmdsynopsis> + <command>coldreboot</command> + <arg choice="opt"> + -<option>hV</option> + </arg> + <arg choice="opt"> + <option>--molly-guard-do-nothing</option> + </arg> + <arg choice="opt"> + -- <replaceable>script_options</replaceable> + </arg> + </cmdsynopsis> + <cmdsynopsis> + <command>pm-hibernate</command> + <arg choice="opt"> + -<option>hV</option> + </arg> + <arg choice="opt"> + <option>--molly-guard-do-nothing</option> + </arg> + <arg choice="opt"> + -- <replaceable>script_options</replaceable> + </arg> + </cmdsynopsis> + <cmdsynopsis> + <command>pm-suspend</command> + <arg choice="opt"> + -<option>hV</option> + </arg> + <arg choice="opt"> + <option>--molly-guard-do-nothing</option> + </arg> + <arg choice="opt"> + -- <replaceable>script_options</replaceable> + </arg> + </cmdsynopsis> + <cmdsynopsis> + <command>pm-suspend-hybrid</command> + <arg choice="opt"> + -<option>hV</option> + </arg> + <arg choice="opt"> + <option>--molly-guard-do-nothing</option> + </arg> + <arg choice="opt"> + -- <replaceable>script_options</replaceable> + </arg> + </cmdsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>DESCRIPTION</title> + + <para> &dhcommand; attempts to prevent you from accidentally shutting down + or rebooting machines. It does this by injecting a couple of checks + before the existing commands: <command>coldreboot</command>, + <command>halt</command>, <command>reboot</command>, + <command>shutdown</command>, <command>poweroff</command>, + <command>pm-hibernate</command>,<command>pm-suspend</command> + and <command>pm-suspend-hybrid</command>.</para> + + <para> Before &dhcommand; invokes the real command, all scripts in + <filename>/etc/molly-guard/run.d/</filename> have to run and exit + successfully; else, it aborts the command. + <command>run-parts(1)</command> is used to process the directory.</para> + + <para> &dhcommand; passes any <replaceable>script_options</replaceable> to the + scripts, and also populates the environment with the following + variables:</para> + + <itemizedlist> + <listitem><para><envar>MOLLYGUARD_CMD</envar> - the actual command + invoked by the user.</para></listitem> + + <listitem><para><envar>MOLLYGUARD_DO_NOTHING</envar> - set to + <option>1</option> if this is a demo-run.</para></listitem> + + <listitem><para><envar>MOLLYGUARD_SETTINGS</envar> - the path to + a shell script snippet which scripts can source to obtain + settings.</para></listitem> + </itemizedlist> + + <para> &dhcommand; prints the contents of + <filename>/etc/molly-guard/messages.d/COMMAND</filename> or + <filename>/etc/molly-guard/messages.d/default</filename> to the console, + if either exists. This is due to + <filename>/etc/molly-guard/run.d/10-print-message</filename>.</para> + + </refsect1> + <refsect1> + <title>GUARDING SSH SESSIONS</title> + + <para> &dhcommand; was primarily designed to shield SSH connections. This + functionality (which should arguably be provided by the + <package>openssh-server</package> package) is implemented in + <filename>/etc/molly-guard/run.d/30-query-hostname</filename>.</para> + + <para> This script first tests whether the command is being executed from + a <filename>tty</filename> which has been created by + <command>sshd</command>. It also checks whether the variable + <envar>SSH_CONNECTION</envar> is defined. If any of these tests are + successful, test script queries the user for the machine's hostname, + which should be sufficient to prevent the user from doing something by + accident.</para> + + <para> You can pass the <option>--pretend-ssh</option> script option to + &dhcommand; to pretend that those tests succeeds. Alternatively, setting + <envar>ALWAYS_QUERY_HOSTNAME</envar> in + <filename>/etc/molly-guard/rc</filename> causes the script to + always query.</para> + + <para> The following situations are still UNGUARDED. If you can think of + ways to protect against those, please let me know!</para> + + <itemizedlist> + <listitem><para>running <application>sudo</application> within + <application>screen</application> or <application>screen</application> within + <application>sudo</application>; <application>sudo</application> eats the + <envar>SSH_CONNECTION</envar> variable, and + <application>screen</application> creates a new + <filename>pty</filename>.</para></listitem> + <listitem><para>executing those command in a remote terminal window, + that is a <application>XTerm</application> started on a remote + machine but displaying on the local <application>X</application> + server.</para></listitem> + </itemizedlist> + + <para> You have been warned. You can use the + <option>--molly-guard-do-nothing</option> switch to prevent anything + from happening, e.g. <userinput>halt + --molly-guard-do-nothing</userinput>. </para> + </refsect1> + + <refsect1> + <title>OPTIONS</title> + <variablelist> + <varlistentry> + <term>--molly-guard-do-nothing</term> + <listitem> + <para> + Cause &dhcommand; to print the command which would be executed, + after processing all scripts, instead of executing it. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>-h</term> + <term>--help</term> + <listitem> + <para> + Display usage information. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>-V</term> + <term>--version</term> + <listitem> + <para> + Display version information. + </para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>SEE ALSO</title> + <para> + <citerefentry> + <refentrytitle>shutdown</refentrytitle> + <manvolnum>8</manvolnum> + </citerefentry>, + <citerefentry> + <refentrytitle>halt</refentrytitle> + <manvolnum>1</manvolnum> + </citerefentry>, + <citerefentry> + <refentrytitle>reboot</refentrytitle> + <manvolnum>8</manvolnum> + </citerefentry>, + <citerefentry> + <refentrytitle>poweroff</refentrytitle> + <manvolnum>8</manvolnum> + </citerefentry>. + <citerefentry> + <refentrytitle>coldreboot</refentrytitle> + <manvolnum>8</manvolnum> + </citerefentry>. + <citerefentry> + <refentrytitle>pm-hibernate</refentrytitle> + <manvolnum>8</manvolnum> + </citerefentry>. + <citerefentry> + <refentrytitle>pm-suspend</refentrytitle> + <manvolnum>8</manvolnum> + </citerefentry>. + <citerefentry> + <refentrytitle>pm-suspend-hybrid</refentrytitle> + <manvolnum>8</manvolnum> + </citerefentry>. + </para> + </refsect1> + + <refsect1> + <title>LEGALESE</title> + + <para> + &dhpackage; is copyright by &dhusername;. Andrew Ruthven came up with + the idea of using the scripts directory and submitted a patch, which + I modified a bit. + </para> + + <para> + This manual page was written by &dhusername; &dhemail;. + </para> + + <para> + Permission is granted to copy, distribute and/or modify this document + under the terms of the Artistic License 2.0 + </para> + + </refsect1> +</refentry> @@ -0,0 +1,19 @@ +# molly-guard settings +# +# ALWAYS_QUERY_HOSTNAME +# When set, causes the 30-query-hostname script to always ask for the +# hostname, even if no SSH session was detected. +#ALWAYS_QUERY_HOSTNAME=true + +# USE_FQDN +# When set, causes the 30-query-hostname script to ask for the fully-qualified +# hostname, rather than the short name +#USE_FQDN=true + +# ANSIBLE_SEARCH_STRING +# The string to search for in the broadcast message provided to shutdown +# set by Ansible. Used to detect if Ansible has initiated a reboot via +# the ansible.builtin.reboot module the default message is "Reboot initiated +# by Ansible" but can be changed by setting the "msg" parameter. This is +# a case insensitive search. +#ANSIBLE_SEARCH_STRING="ansible" diff --git a/run.d/10-print-message b/run.d/10-print-message new file mode 100755 index 0000000..0e19526 --- /dev/null +++ b/run.d/10-print-message @@ -0,0 +1,22 @@ +#!/bin/sh +# +# 10-print-message - print a (command-specific or default) message +# +# Copyright © Andrew Ruthven <andrew@etc.gen.nz> +# Copyright © martin f. krafft <madduck@madduck.net> +# Released under the terms of the Artistic Licence 2.0 +# +# Prints either /etc/molly-guard/messages.d/$MOLLYGUARD_CMD +# or /etc/molly-guard/messages.d/default +# depending on whether the first exists. +# +set -eu + +MESSAGESDIR=/etc/molly-guard/messages.d + +for i in $MOLLYGUARD_CMD default; do + if [ -f "$MESSAGESDIR/$i" ] && [ -r "$MESSAGESDIR/$i" ]; then + cat $MESSAGESDIR/$i + exit 0 + fi +done diff --git a/run.d/30-query-hostname b/run.d/30-query-hostname new file mode 100755 index 0000000..da5c6b8 --- /dev/null +++ b/run.d/30-query-hostname @@ -0,0 +1,102 @@ +#!/bin/sh +# +# 30-ask-hostname - request the user to type in the hostname of the local host +# +# Copyright © 2006-2009 martin f. krafft <madduck@madduck.net> +# Copyright © 2012 Ludovico Gardenghi <lu@dovi.co> +# Copyright © 2014 Josh Triplett <josh@joshtriplett.org> +# Copyright © 2015 Francois Marier <francois@debian.org> +# Copyright © 2017 Simó Albert i Beltran <sim6@probeta.net> +# Copyright © 2023 Andrew Ruthven <andrew@etc.gen.nz> +# Released under the terms of the Artistic Licence 2.0 +# +set -eu + +ME=molly-guard + +# Skip if we're being run by configuration management. +[ $CONFIGURATION_MANAGEMENT -eq 1 ] && exit 0 + +# Walk up the process tree until PID 1 is reached or a process with 'sshd' in +# its /proc/<pid>/cmdline is met. Return success if such a process is found. +is_child_of_sshd_or_mosh_server() { + pid=$$ + ppid=$PPID + # Be a bit paranoid with the guard, should some horribly broken system + # provide a strange process hierarchy. '[ $pid -ne 1 ]' should be enough for + # sane systems. + [ -z "$pid" ] || [ -z "$ppid" ] && return 2 + while [ $pid -gt 1 ] && [ $pid -ne $ppid ]; do + if egrep -q 'sshd|mosh-server|etterminal' /proc/$ppid/cmdline; then + return 0 + fi + pid=$ppid + ppid=$(grep ^PPid: /proc/$pid/status | tr -dc 0-9) + done + return 1 +} + +[ -f "$MOLLYGUARD_SETTINGS" ] && . "$MOLLYGUARD_SETTINGS" + +PRETEND_SSH=0 +for arg in "$@"; do + case "$arg" in + (*-pretend-ssh) PRETEND_SSH=1;; + esac +done + +# require an interactive terminal connected to stdin +test -t 0 || exit 0 + +# we've been asked to always protect this host +case "${ALWAYS_QUERY_HOSTNAME:-0}" in + 0|false|False|no|No|off|Off) + # only run if we are being called over SSH, that is if the current terminal + # was created by sshd. + command -v tty >/dev/null 2>&1 || exit 0 + PTS=$(tty) + if ! pgrep -f "^sshd.+${PTS#/dev/}\>" >/dev/null \ + && [ -z "${SSH_CONNECTION:-}" ] \ + && ! is_child_of_sshd_or_mosh_server; then + if [ $PRETEND_SSH -eq 1 ]; then + echo "I: $ME: this is not an SSH session, but --pretend-ssh was given..." >&2 + else + exit 0 + fi + else + echo "W: $ME: SSH session detected!" >&2 + fi + ;; + *) + echo "I: $ME: $MOLLYGUARD_CMD is always molly-guarded on this system." >&2 + ;; +esac + +case "${USE_FQDN:-0}" in + 0|false|False|no|No|off|Off) + HOSTNAME="$(hostname --short)" + ;; + *) + HOSTNAME="$(hostname --fqdn)" + ;; +esac + +sigh() +{ + echo "Good thing I asked; I won't $MOLLYGUARD_CMD $HOSTNAME ..." >&2 + exit 1 +} + +trap 'echo;sigh' 1 2 3 9 10 12 15 + +echo -n "Please type in hostname of the machine to $MOLLYGUARD_CMD: " +read HOSTNAME_USER || : + +HOSTNAME="$(echo "$HOSTNAME" | tr '[:upper:]' '[:lower:]')" +HOSTNAME_USER="$(echo "$HOSTNAME_USER" | tr '[:upper:]' '[:lower:]')" + +[ "$HOSTNAME_USER" = "$HOSTNAME" ] || sigh + +trap - 1 2 3 9 10 12 15 + +exit 0 diff --git a/shutdown.in b/shutdown.in new file mode 100755 index 0000000..3216933 --- /dev/null +++ b/shutdown.in @@ -0,0 +1,152 @@ +#!/bin/sh +# +# shutdown -- wrapper script to guard against accidental shutdowns +# +# Copyright © martin f. krafft <madduck@madduck.net> +# Released under the terms of the Artistic Licence 2.0 +# +set -eu + +ME=molly-guard +VERSION=0.8 + +SCRIPTSDIR="@cfgdir@/run.d" + +CMD="${0##*/}" + +case "$CMD" in + halt|reboot|shutdown|poweroff|coldreboot|pm-hibernate|pm-suspend|pm-suspend-hybrid) + if ! EXEC=$(command -v "$CMD.no-molly-guard"); then + if ! EXEC=$(command -v "$CMD.no-molly-guard.usr-is-merged"); then + echo "E: not a regular file: $EXEC" >&2 + exit 4 + fi + fi + if [ "$EXEC" -ef /usr/lib/molly-guard/molly-guard ]; then + # Symlink forwards to ourselves. Resolve! + LINKTARGET=$(readlink "$EXEC") + if ! EXEC=$(command -v "$LINKTARGET.no-molly-guard"); then + if ! EXEC=$(command -v "$LINKTARGET.no-molly-guard.usr-is-merged"); then + echo "E: not a regular file $EXEC" >&2 + exit 4 + fi + fi + fi + if [ ! -x $EXEC ]; then + echo "E: not an executable: $EXEC" >&2 + exit 3 + fi + ;; + *) + echo "E: unsupported command: $CMD" >&2 + exit 1 + ;; +esac + +usage() +{ + cat <<-_eousage + Usage: $ME [options] [-- script options] + (shielding $EXEC) + + molly-guard's primary goal is to guard against accidental + shutdowns/reboots. $ME will run all scripts in $SCRIPTSDIR and only + invokes $EXEC if all scripts exited successfully. + + Specifying --molly-guard-do-nothing as argument to the command will + make $ME echo the command it would execute rather than actually + executing it. + + Options following the double hyphen will be passed unchanged to the + scripts. + + Please see molly-guard(8) for more information. + + The actual command's help output follows: + + _eousage +} + +CMDARGS= +SCRIPTARGS= +END_OF_ARGS=0 +DO_NOTHING=0 +for arg in "$@"; do + case "$arg" in + (*-molly-guard-do-nothing) DO_NOTHING=1;; + (*-help) + usage 2>&1 + ( exec bash -c 'exec -a "$0" "$@"' "$CMD" "$EXEC" --help 2>&1 ) + exit 0 + ;; + --) END_OF_ARGS=1;; + *\"*) + echo 'E: cannot use double-quotes (") in arguments' >&2 + exit 1 + ;; + *) + if [ $END_OF_ARGS -eq 0 ]; then + CMDARGS="${CMDARGS:+$CMDARGS }\"$arg\"" + else + SCRIPTARGS="${SCRIPTARGS:+$SCRIPTARGS }--arg \"$arg\"" + fi + ;; + esac +done + +do_real_cmd() +{ + if [ $DO_NOTHING -eq 1 ]; then + echo "$ME: would run: $EXEC $CMDARGS" + exit 0 + else + eval exec bash -c "'"'exec -a "$0" "$@"'"'" "'$CMD'" "'$EXEC'" "$CMDARGS" + fi +} + +if [ $DO_NOTHING -eq 1 ]; then + echo "I: demo mode; $ME will not do anything due to --molly-guard-do-nothing." >&2 +fi + +if [ -n "${MOLLYGUARD_CMD:-}" ]; then + do_real_cmd +fi + +MOLLYGUARD_CMD=$CMD; export MOLLYGUARD_CMD +MOLLYGUARD_DO_NOTHING=$DO_NOTHING; export MOLLYGUARD_DO_NOTHING +MOLLYGUARD_SETTINGS="@cfgdir@/rc"; export MOLLYGUARD_SETTINGS + +# pass through certain commands +case "$CMD $CMDARGS" in + (*shutdown\ *-c*|*halt\ *-w*|*halt\ *-f*|*reboot\ *-f*) + # allow canceling shutdowns, only write wtmp and force immediate halt + echo "I: executing $CMD $CMDARGS regardless of check results." >&2 + do_real_cmd + ;; +esac + +# Check for execution from configuration management tools that might use an +# interactive shell. +CONFIGURATION_MANAGEMENT=0 + +[ -f "$MOLLYGUARD_SETTINGS" ] && . "$MOLLYGUARD_SETTINGS" + +# Ansible uses an interactive shell, but by default puts Ansible in the +# broadcast message, we can look for that. Allow for it be changed per site. +ANSIBLE_SEARCH_STRING="${ANSIBLE_SEARCH_STRING:-ansible}" +if echo $CMDARGS | grep -qi $ANSIBLE_SEARCH_STRING; then + CONFIGURATION_MANAGEMENT=1 +fi + +export CONFIGURATION_MANAGEMENT + +for script in $(run-parts --test $SCRIPTSDIR); do + ret=0 + eval $script $SCRIPTARGS || ret=$? + if [ $ret -ne 0 ]; then + echo "W: aborting $CMD due to ${script##*/} exiting with code $ret." >&2 + exit $ret + fi +done + +do_real_cmd |