#!/usr/afsws/bin/pagsh.krb
# $Id: runauth,v 1.15 2009-05-18 01:58:14 eagle Exp $
#
# runauth -- Runs a program with a ticket and optionally a token.
#
# Written by Russ Allbery <rra@stanford.edu>
# With some code by Carol Oliver
#
# Copyright 1998, 1999, 2000, 2001, 2002, 2003, 2005
#     Board of Trustees, Leland Stanford Jr. University
#
# This is a wrapper around ksrvtgt or kstart that obtains a ticket from a
# local srvtab file, optionally also obtains a token, and then runs a command
# as a specified user (or as root if no user is given), cleaning up the
# authentication credentials afterwards.  By default this script uses ksrvtgt,
# so the obtained credentials are only good for five minutes.
#
# If -a is specified, kstart is used instead of ksrvtgt to obtain a ticket
# with a longer than five minute lifetime.  By default -a will get a 60 minute
# ticket, but -l can be specified to get a different lifetime.
#
# Usage: runauth [-a] [-t] [-f <srvtab>] [-p <principal>] [-u <user>]
#           [-l <lifetime>] [-r <realm>] <command>

# Paths to programs used.  At Stanford, this depends on AFS and pubsw.
afslog_path=/usr/pubsw/bin/aklog
kdestroy_path=/usr/pubsw/bin/kdestroy
kstart_path=/usr/pubsw/bin/kstart
ksrvtgt_path=/usr/pubsw/bin/ksrvtgt
ps_path=/usr/bin/ps
setuidgid_path=/usr/pubsw/bin/setuidgid

# Read in the options, if any.
srvtab=''
principal=''
realm=''
user=''
kstart=false
lifetime='60'
token=false
options=true
while $options ; do
    case X"$1" in
    X-a)
         kstart=true
         ;;
    X-f)
         shift
         srvtab="$1"
         ;;
    X-l)
         shift
         lifetime="$1"
         ;;
    X-p)
         shift
         name=`echo "$1" | sed 's/\..*//'`
         if echo "$1" | grep '\.' >/dev/null 2>&1 ; then
             instance=`echo "$1" | sed 's/^[^.]*\.//'`
         else
             instance=''
         fi
         ;;
    X-r)
         shift
         realm="$1"
         ;;
    X-t)
         token=true
         ;;
    X-u)
         shift
         user="$1"
         ;;
    X-*)
         echo "$0: Illegal option -- $1" 1>&2
         exit 1
         ;;
    *)
         options=false
         ;;
    esac
    $options && shift
done

# If no principal was specified, use the default one of rcmd.`hostname`.
if [ -z "$name" ] ; then
    name='rcmd'
    instance=`hostname | sed 's/\..*//'`
fi

# Grab the rest.
command="$@"
if [ -z "$command" ] ; then
    exec 1>&2
    echo 'Usage: runauth [-a] [-t] [-f <srvtab>] [-p <principal>] [-u <user>]'
    echo '           [-l <lifetime>] [-r <realm>] <command>'
    exit 1
fi

# Now, do the real work.  First, obtain the ticket.  If we're using kstart and
# the lifetime is longer than 25 hours, we need to background kstart and will
# need to kill it again when the job finishes.
child=false
if [ x"$kstart" = xtrue ] ; then
    if [ -z "$srvtab" ] ; then
        srvtab='/etc/leland/srvtab'
    fi
    auth="$kstart_path -q -f '$srvtab' -u '$name' -i '$instance'"
    if [ -n "$realm" ] ; then
        auth="$auth -r '$realm'"
    else
        if [ x"$token" = xtrue ] ; then
            auth="$auth -t"
        fi
    fi
    if [ "$lifetime" -gt 1500 ] ; then
        child=true
        auth="$auth -K 60 &"
    else
        auth="$auth -l '$lifetime'"
    fi
else
    if [ -n "$realm" -a -z "$srvtab" ] ; then
        srvtab='/etc/leland/srvtab'
    fi
    auth="$ksrvtgt_path '$name' '$instance'"
    if [ -n "$srvtab" ] ; then
        if [ -n "$realm" ] ; then
            auth="$auth '$realm' '$srvtab'"
        else
            auth="$auth '$srvtab'"
        fi
    fi
fi
eval $auth
status=$?
if [ $status != 0 ] ; then
    echo "$0: Error obtaining Kerberos ticket" 1>&2
    exit $status
fi
if [ x"$child" = xtrue ] ; then
    sleep 1
fi

# Okay, we now have a ticket.  Obtain a token if we were told to.
if [ x"$token" = xtrue ] ; then
    if [ x"$kstart" != xtrue -o -n "$realm" ] ; then
        if [ -z "$realm" ] ; then
            "$afslog_path"
        else
            # Realm needs to be lowercased.
            realm=`echo $realm | tr A-Z a-z`
            "$afslog_path" -c "$realm"
        fi
        status=$?
        if [ $status != 0 ] ; then
            echo "$0: Error obtaining AFS token" 1>&2
            exit $status
        fi
    fi
fi

# We're authenticated; run the command.  Always start from / if we're not
# root, since otherwise we may be in a protected directory.
if [ -z "$user" ] ; then
    $command
else
    cd /
    "$setuidgid_path" "$user" $command
fi
status=$?

# Succeed or fail, we now destroy our credentials.
if [ x"$child" = xtrue ] ; then
    me="$$"
    process=`"$ps_path" -eo pid,ppid,comm | grep " $me " | grep kstart`
    if [ -n "$process" ] ; then
        kill -HUP `echo "$process" | sed -e 's/^ *//' -e 's/ .*//'`
    fi
fi
"$kdestroy_path" -4 > /dev/null 2>&1

# Exit with the same exit status as su or the command.
exit $status


# 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__

=head1 NAME

runauth - Runs a program with a ticket and optionally a token.

=head1 SYNOPSIS

B<runauth> [B<-a>] [B<-t>] [B<-f> I<srvtab>] [B<-p> I<principal>]
S<[B<-u> I<user>]> S<[B<-l> I<lifetime>]> S<[B<-r> I<realm>]> I<command>

=head1 REQUIREMENTS

B<runauth> uses either B<ksrvtgt> or B<kstart> to obtain Kerberos tickets.
It is designed to work with Kerberos v4, but can be easily changed to use
Kerberos v5 by pointing it at B<k5start> instead.  For the B<-t> option,
some program to obtain AFS tokens from a ticket is needed (normally B<aklog>
or B<afslog>).  For the B<-u> option, B<setuidgid> from daemontools is used.
To support long ticket lifetimes with B<kstart>, a version of B<ps> that
supports the standard System V B<-o> option is required.

The paths to all supporting programs used are set at the beginning of this
script.

=head1 DESCRIPTION

This is a wrapper around B<ksrvtgt> or B<kstart> that obtains a ticket from
a local srvtab file, optionally also obtains a token, and then runs
I<command> as a specified user (or as root if no user is given with B<-u>),
cleaning up the authentication credentials afterwards.  By default this
script uses B<ksrvtgt>, so the obtained credentials are only good for five
minutes.

This script depends on AFS and pubsw, and uses hard-coded paths to pubsw
programs so that it has no dependencies on a properly set PATH or any local
software installation.  The command is run inside a PAG shell to isolate its
credentials from other programs running on the same system.  It is suitable
for use in cron jobs or programs run infrequently from .forward or the
equivalent.

=head1 OPTIONS

=over 4

=item B<-a>

Use B<kstart> instead of B<ksrvtgt> to obtain the ticket.  By default,
B<runauth> uses B<ksrvtgt> and therefore obtains a ticket with only a five
minute lifetime.  This flag causes it to use B<kstart> instead and get a
ticket with an hour lifetime (this can be increased with the B<-l> flag).

=item B<-f> I<srvtab>

Use I<srvtab> as the srvtab to authenticate to Kerberos rather than the
default of F</etc/leland/srvtab>.

=item B<-l> I<lifetime>

Obtain a ticket of lifetime I<lifetime> (in minutes) rather than the default
(with B<-a>) of one hour.  This option only makes sense in combination with
B<-a>.  If I<lifetime> is longer than 25 hours, B<kstart> will be run in
daemon mode and killed when the command exits.

=item B<-p> I<principal>

Authenticate as I<principal> rather than the default of rcmd.I<machine>
where I<machine> is the current host name (stripped of anything following
the first period).  Note that I<principal> must use Kerberos v4 syntax,
namely I<name>.I<instance>, and not Kerberos v5 syntax.

=item B<-r> I<realm>

Obtain tickets and tokens in the realm I<realm> rather than the default
realm.

=item B<-t>

Obtain an AFS token as well as a ticket (by running aklog(1) after obtaining
a ticket).

=item B<-u> I<user>

Run the actual command as the user I<user> rather than as root.  Use of this
option is recommended for any command that doesn't have to actually run as
root on the local machine for additional security.  If this option is given,
I<command> will be run using setuidgid(8).

=back

=head1 EXAMPLES

This command:

    runauth -t -u news /news/local/archivegroups -v

runs C</news/local/archivegroups -v> as the user news, after obtaining
tickets and tokens using the rcmd srvtab for the current machine.

This command:

    runauth -a -l 180 -f /etc/leland/srvtab.imap -p service.imap \
        /usr/local/bin/check-imap

obtains a ticket for service.imap using F</etc/leland/srvtab.imap> as the
srvtab, using kstart and obtaining a ticket with a three-hour lifetime, and
then runs C</usr/local/bin/check-imap>.

=head1 FILES

=over 4

=item F</etc/leland/srvtab>

The default srvtab used to obtain a ticket.

=back

=head1 BUGS

When run with a lifetime of longer than 25 hours, at least Solaris sh prints
out an extraneous message when B<kstart> is killed after the job completes,
looking like:

    193 Hangup

There seems to be no way to silence this message without rewriting this
script in a saner programming language.

Getting tokens in an external realm with a lifetime of longer than 25 hours
doesn't work, since B<kstart> doesn't know how to run B<aklog> with the right
arguments currently.

The boundary at which B<kstart> is run in daemon mode is chosen for the 25
hour default ticket lifetime at Stanford and may not be appropriate for other
sites.

=head1 SEE ALSO

aklog(1), ksrvtgt(1), k5start(1), kstart(1), setuidgid(8)

B<runauth> was tested with the B<afslog> implementation from KTH
Kerberos.  B<setuidgid> is part of daemontools, available from
L<http://cr.yp.to/daemontools.html>.  B<kstart> and B<k5start> are
available from L<http://www.eyrie.org/~eagle/software/kstart/>.

L<http://www.eyrie.org/~eagle/software/runauth/> will have the current
version of this program.

=head1 AUTHOR

Russ Allbery <rra@stanford.edu>, with contributions from Carol Oliver.

=head1 COPYRIGHT AND LICENSE

Copyright 1998, 1999, 2000, 2001, 2002, 2003, 2005 Board of Trustees, Leland
Stanford Jr. University.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.

=cut

__END_OF_DOCS__