#!/bin/sh
#
# rtripwire -- Run tripwire database updates remotely.
#
# Written by Russ Allbery <rra@stanford.edu>
# Copyright 2000, 2001, 2002, 2003, 2007, 2008, 2011, 2012, 2013
#     The Board of Trustees of the Leland Stanford Junior University
#
# This shell script was written to provide the ability to run update the
# tripwire database of a remote system without having to log on to that system
# (thus making it easier to do things like update a set of systems in a loop
# and the like).
#
# It assumes that you have both a system:administrators AFS token and a root
# ticket (or some ticket suitable for doing remctl or ssh -l root to a system
# with).  You must obtain both before running this script.
#
# Syntax is simple:
#
#       rtripwire [-rsx] <system> <options>
#
# The -r and -x options are ignored for backwards compatibility.  If the -s
# option is given, rtripwire uses ssh -l root to run tripwire.  Otherwise, the
# remctl interface is used, and -interactive is not supported.
#
# rtripwire assumes that the result of running tripwire will be a database
# located in /root/tmp/databases on the remote system; this should be
# compatible with -initialize, -update, and -interactive.  rtripwire will
# determine automatically whether the system's tripwire database is in
# /afs/ir/site/leland/tripwire or in /afs/ir/site/sweet/tripwire.
#
# Multiple system names are not permitted; to run tripwire on multiple systems
# in sequence, use a shell for loop.
#
# rtripwire assumes the existence of a /root/tmp directory on the remote
# system, assumes that this directory will not be checked by tripwire, and
# assumes that it's safe to put tripwire database files into that directory.

PATH=/usr/local/bin:/usr/bin:/bin:/usr/kerberos/bin
export PATH

if [ $# = 0 ] ; then
    echo 'Usage: rtripwire [-rsx] <system> <options>' >&2
    exit 1
fi

# Parse options.
remctl=true
while getopts 'rsx' option ; do
    case $option in
        r)  ;;
        s)  remctl= ;;
        x)  ;;
        \?) exit 1 ;;
    esac
done
shift `expr $OPTIND - 1`

# Pull off the system name.
system="$1"
shift
if [ -z "$system" ] ; then
    echo 'Invalid empty system name' >&2
    exit 1
fi

# As a cute hack, check to see if the first option to tripwire will be -q.
# If so, we're trying to be quiet and tripwire may not produce much output
# (a tripwire -q -update, for example), so don't output our blank lines.
if [ x"$1" = x-q ] ; then
    if [ x"$remctl" = xtrue ] ; then
        echo 'Quiet mode not (yet) supported with remctl' >&2
        exit 1
    fi
    quiet=/bin/true
else
    quiet=/bin/false
fi

# Find the tripwire database directory and the right tripwire binary.
dir="none"
for d in "/afs/ir/site/leland/tripwire/$system" \
         "/afs/ir/site/leland/tripwire/$system.Stanford.EDU" \
         "/afs/ir/site/leland/tripwire/$system.stanford.edu" \
         "/afs/ir/site/leland/tripwire/$system.sunet" \
         "/afs/ir/site/leland/tripwire/$system.oit.duke.edu" ; do
    if [ -d "$d" ] ; then
        dir="$d"
        break
    fi
done
if [ x"$dir" = xnone ] ; then
    echo "Cannot find database directory for $system" >&2
    exit 1
fi

# Determine who the user is and figure out what user or group to add to the
# ACLs and then remove again.  Note that this is all unnecessary for the case
# where we're just examining the tripwire report and could be skipped, but
# I've not done that work yet.
user=`klist -5 2>&1 | grep -i 'principal:' | sed -e 's/@.*//' -e 's/.* //'`
user=`echo "$user" | sed -e 's%/admin$%/root%' -e 's%/%.%'`
if [ -z "$user" ] ; then
    echo 'Unable to determine Kerberos identity' >&2
    exit 1
fi
acluser=
if pts membership "$user" 2>&1 | grep "doesn't exist" > /dev/null 2>&1 ; then
    acluser=`echo "$user" | sed 's/\.root$//'`
else
    for g in system:administrators \
             workgroup:itservices_idg_root \
             workgroup:itservices_sa-app_root \
             workgroup:itservices_sa-core_root \
             workgroup:itservices_sa-rc_root \
             workgroup:crc_crcsg_root ; do
        if pts membership "$user" | grep "$g\$" > /dev/null 2>&1 ; then
            acluser="$g"
            break
        fi
    done
fi
if [ -z "$acluser" ] ; then
    acluser="$user"
fi

# Run tripwire on the remote system from /root/tmp.
echo "Running tripwire remotely on $system"
$quiet || echo ''
if [ -z "$remctl" ] ; then
    ssh -l root "$system" "cd /root/tmp && $dir/../bin/tripwire $*"
    if [ $? != 0 ] ; then
        echo "ssh to $system failed, giving up" >&2
        exit 1
    fi

    # If we weren't given any function arguments, we're now all done.
    if [ $# = 0 ] ; then
        exit 0
    fi

    # Go to the right directory, give ourselves write permission, and pull the
    # new database over.  We don't know if the previous command succeeded, so
    # this is where things will fail if it didn't.  mv the old database aside
    # and restore it if the rcp doesn't work.
    $quiet || echo ''
    cd "$dir" || exit 1
    echo "Copying new database file from $system..."
    fs setacl . "$acluser" all
    if [ $? != 0 ] ; then
        echo "Unable to grant write privilege on $dir" >&2
        exit 1
    fi
    if [ -r tw.db ] ; then
        mv -f tw.db tw.db.old
        if [ $? != 0 ] ; then
            echo 'Unable to save old database file' >&2
            fs setacl . "$acluser" rla
            exit 1
        fi
    fi
    scp root@"$system":/root/tmp/databases/tw.db .
    if [ $? != 0 ] ; then
        echo 'Unable to copy over new database file' >&2
        mv -i tw.db.old tw.db
        fs setacl . system:administrators none
        exit 1
    fi
    fs setacl . "$acluser" rla

    # Clean up the mess left behind in /root/tmp on the remote system.
    echo "Cleaning up /root/tmp on $system..."
    ssh -l root "$system" '/bin/rm -rf /root/tmp/databases'

else

    # Handle check separately.
    if [ $# = 0 ] ; then
        exec remctl "$system" tripwire check
    fi

    # Change to the database directory and grant ourselves access on it.
    cd "$dir" || exit 1
    fs setacl . "$acluser" all
    if [ $? != 0 ] ; then
        echo "Unable to grant write privilege on $dir" >&2
        exit 1
    fi

    # Pull off the command and map it to the remctl command.
    command="$1"
    shift
    case "$command" in
      -interactive)
        echo "-interactive not supported with remctl interface" >&2
        exit 1
        ;;
      -initialize|-init)
        command=init
        ;;
      -update)
        command=update
        ;;
      *)
        echo "Unknown command $command" >&2
        exit 1
        ;;
    esac

    # Run the remctl command and save the new database.
    remctl "$system" tripwire "$command" "$@" > tw.db.new
    if [ $? != 0 ] ; then
        echo "tripwire command failed" >&2
        exit 1
    fi
    mv tw.db.new tw.db
    if [ $? != 0 ] ; then
        echo 'Unable to save old database file' >&2
        fs setacl . "$acluser" rla
        exit 1
    fi
    fs setacl . "$acluser" rla
fi

# Reset the last clean time on the tripwire database if we updated the
# database.
remctl devnull tripwire-report clean "$system"

# All done.
echo Success.
$quiet || echo ''
exit 0


# Documentation.  Use a hack to hide this from the shell.  Because of the
# above exit line, this should never be executed.
DOCS=<<__END_OF_DOCS__

=for stopwords
AFS Allbery PTS tripwire-backend -init remctl rtripwire scp

=head1 NAME

rtripwire - Run tripwire database updates remotely

=head1 SYNOPSIS

B<rtripwire> [B<-s>] I<system> [B<-init> | B<-initialize> | B<-interactive>]

B<rtripwire> [B<-s>] I<system> B<-update> I<file> [I<file> ...]

=head1 DESCRIPTION

B<rtripwire> runs tripwire remotely on a system and handles updating the
tripwire database stored in AFS when appropriate.  It handles
automatically changing the AFS ACLs on the tripwire database directory to
make it writable by the user running B<rtripwire> and then reverting that
change after the database has been updated.

B<rtripwire> has two modes.  By default, it uses the remctl interface to
run tripwire on the server.  This requires that the new
B<tripwire-backend> script and corresponding remctl configuration be
installed on the server.  With the B<-s> option, it uses B<ssh> and B<scp>
to run tripwire on the system, save the output into F</root/tmp>, and then
copy it back from the system into AFS.  The remctl mode is preferable if
available since it doesn't have the firewall requirements of Kerberos ssh.
However B<-s> is required in order to use B<-interactive>.

The operation to perform on the remote system is given after the system
name.  B<-init> or B<-initialize> generates a new database.  B<-update>
takes a list of paths and updates the database using the current
information about each of those paths, and is the preferred way of
clearing a dirty tripwire report.  B<-interactive> displays the changes
interactively and asks for approval on each one.  If all those options are
omitted, B<rtripwire> runs a regular tripwire check on the system and
prints out the report.

If the tripwire database was updated successfully, B<rtripwire> clears the
last-clean time on the database via the C<tripwire-report clean> remctl
interface on devnull.stanford.edu.

=head1 OPTIONS

=over 4

=item B<-r>

Ignored for backward compatibility.

=item B<-s>

As described above, use the Kerberos B<rsh> interface rather than the
remctl interface.

=item B<-x>

Ignored for backward compatibility.

=back

=head1 BUGS

The logic for checking which PTS group to use to give the user access to
the tripwire database directory should be skipped unless one of the update
options is given.

This script would benefit greatly from being restructured to use shell
functions.

=head1 AUTHOR

Russ Allbery <rra@stanford.edu>

=head1 SEE ALSO

remctl(1), ssh(1), tripwire(8), tripwire-backend(8)

=cut

__END_OF_DOCS__
