#! /usr/bin/perl
$ID = q$Id: krsh,v 1.9 2003/07/02 17:40:34 eagle Exp $;
#
# krsh -- Forward tickets when running rsh.
#
# Copyright 1996, 2001, 2002, 2003
#     Board of Trustees, Leland Stanford Jr. University

##############################################################################
# Site configuration
##############################################################################

# Find locations of Kerberos programs.  These directories are searched before
# the user path for programs run on the local host.  For programs run on the
# remote host, the user's path is always used.  /usr/pubsw/bin is the standard
# Stanford location, /usr/local/bin is used by stow packages, and
# /etc/leland/bin was used by Kerberos kits.
@PATH = qw(/usr/pubsw/bin /usr/local/bin /etc/leland/bin);

##############################################################################
# Modules and declarations
##############################################################################

require 5.004;

use Getopt::Long qw(GetOptions);

use strict;
use vars qw(%CONFIG $ID @PATH %PATH);

##############################################################################
# Option parsing
##############################################################################

# Print out a brief help message and exit.  (Used for -h or --help on the
# command line).
sub usage {
    print <<"EOM";
Usage: krsh [-hvfqV] [-l username] host command

krsh optionally forwards your Kerberos TGT to the remove host and then
performs a Kerberized rsh to that system.  If your ticket is forwarded, it
wraps the supplied command with aklog before the command and kdestroy and
unlog after the command, to obtain an AFS token and to clean it and the
ticket up after the command has executed.

    -h          Print this message and exit.
    -v          Print the version number and exit.
    -f          Do not forward your ticket.
    -l user     Log in as user (defaults to the local username).
    -q          Tell kftgt to be quiet.
    -V          Display each command before executing it.
    -x          Encrypt the connection.

EOM
    exit 0;
}

# Get the fully qualified name for a host.  We do this because the real name
# may vary from the name given on the command line if they're going to a
# load-balanced address and because we want to trim off the domain if it's the
# same as our domain.
sub qualify {
    my ($host) = @_;
    my ($type, $realhost) = (gethostbyname $host)[2,4];
    $realhost = gethostbyaddr ($realhost, $type);
    die "$0: unknown host $host\n" unless $realhost;
    eval { require Net::Domain };
    unless ($@) {
        my $domain = Net::Domain::hostdomain ();
        $realhost =~ s/\Q.$domain\E$//i;
    }
    $realhost;
}

# Find a programs that we care about.  We search @PATH and then the user's
# PATH.  It would be better to search the user's PATH first, but that makes it
# too likely that we'll find a non-Kerberos rsh.  If we can't find the
# executable, we return the first path in @PATH (which will probably then fail
# later).
sub search_path {
    my $program = shift;
    for (@PATH, split (':', $ENV{PATH})) {
        return "$_/$program" if -x "$_/$program";
    }
    return "$PATH[0]/$program";
}

# Set up the %PATH hash with the locations of the programs we care about,
# honoring environment variables for each one.
sub find_programs {
    $PATH{kftgt}  = $ENV{'KLOGIN_KFTGT_PROGRAM'}  || search_path 'kftgt';
    $PATH{rsh}    = $ENV{'KLOGIN_RSH_PROGRAM'}    || search_path 'rsh';
}

# Parse command-line options, putting the results into the global %CONFIG
# hash.  Note that krsh *reverses* the default meaning of -f, but does *not*
# reverse the default meaning of -x (unlike klogin, which reverses both of
# them).  Note that it also doesn't support all of the various rsh flags.
# Returns the host and command.
sub parse_options {
    my ($host, $command);

    Getopt::Long::config ('bundling_override');
    GetOptions (\%CONFIG,'help|h', 'version|v', 'noforward|f',
                'login|user|l=s', 'quiet|q', 'verbose|V',
                'encrypt|x') or exit 1;

    # If they requested usage or version, give them that and exit.
    if ($CONFIG{help}) { usage }
    if ($CONFIG{version}) {
        my $version = join (' ', (split (' ', $ID))[1..3]);
        $version =~ s/,v\b//;
        $version =~ s/(\S+)$/($1)/;
        $version =~ tr%/%-%;
        print $version, "\n";
        exit;
    }

    # I'd really prefer not to support this, but we're stuck with it.  Which
    # means I should go back and add in support for all of the flags we
    # support.  And some way to turn off on the command line something that's
    # turned on in the environment....
    local $_;
    if ($_ = $ENV{'KLOGIN_OPTS'}) {
        $CONFIG{noforward} ||= /f/;
        $CONFIG{verbose}   ||= /V/;
    }

    # Check the arguments.
    $host = shift @ARGV;
    die "$0: no host specified (-h for help)\n" unless defined $host;
    $command = join (' ', @ARGV) if @ARGV;

    # Do a forward and then reverse name lookup on the hostname.  This is
    # required because of our load-balancing system; otherwise, we may forward
    # our ticket to one host and then try to rsh to another.  We also want to
    # pass rsh the correct hostname so that it knows which principal to use.
    $host = qualify ($host);

    return ($host, $command);
}

##############################################################################
# Running programs
##############################################################################

# Forward the user's tgt to the remote host using kftgt.  We have to look for
# a -l command line option and be sure to pass that along to kftgt as well,
# and also honor the -q option.
sub forward_ticket {
    my ($host) = @_;
    my @command = $PATH{kftgt};
    push (@command, '-l', $CONFIG{login}) if $CONFIG{login};
    push (@command, '-q') if $CONFIG{quiet};
    push (@command, $host);
    print "@command\n" if $CONFIG{verbose};
    system (@command) == 0
        or die "$0: $command[0] failed with exit status ", ($? >> 8), "\n";
}

# Run rsh to the remote system.
sub rsh {
    my ($host, $command) = @_;
    my @command = $PATH{rsh};
    push (@command, '-l', $CONFIG{login}) if $CONFIG{login};
    push (@command, '-x') if $CONFIG{encrypt};
    push (@command, $host);
    if ($command) {
        $command = "aklog; $command; sh -c 'kdestroy > /dev/null 2>&1'; unlog"
            unless $CONFIG{noforward};
        push (@command, $command);
    }
    if ($CONFIG{verbose}) {
        my @print = @command;
        $print[-1] = "'$print[-1]'";
        print "@print\n";
    }
    system (@command) == 0
        or die "$0: $command[0] failed with exit status ", ($? >> 8), "\n";
}

##############################################################################
# Main routine
##############################################################################

# Trim extraneous path garbage from the program name.
$0 =~ s%.*/%%;

# Find the programs we care about.
find_programs;

# Parse the command line options to determine what to do.
my ($host, $command) = parse_options;

# Optionally forward our ticket.
forward_ticket ($host) unless $CONFIG{noforward};

# Do the actual rsh.
rsh ($host, $command);

##############################################################################
# Documentation
##############################################################################

=head1 NAME

krsh - Kerberos rsh with v4 ticket forwarding

=head1 SYNOPSIS

krsh [B<-fhqvVx>] [B<-l> I<username>] I<host> I<command> ...

=head1 DESCRIPTION

B<krsh> forwards your Kerberos ticket-granting ticket to the machine
I<host> using B<kftgt> and then executes I<command> on that system using
Kerberos rlogin.  It assumes that the remote system has B<aklog>,
B<kdestroy>, and B<unlog> installed on your default path on the remote
system.

Normally it's not necessary to forward one's ticket to execute commands
remotely with rsh.  The reason for the existence of this wrapper is to
facilitate running programs that need to write to disk on a remote system
that uses AFS.  The commands given to B<krsh> will be wrapped by an
execution of B<aklog> before the command to obtain an AFS token from the
forwarded ticket and B<kdestroy> and B<unlog> after the command to destroy
the forwarded ticket and obtained token.

The host given to B<krsh> is put through a forward and then reverse DNS
lookup before being used, to resolve any CNAMEs to their canonical hosts
and to handle load-balanced hosts or hosts with multiple A records.

=head1 OPTIONS

=over 4

=item B<-f>, B<--noforward>

Don't forward tickets to the remote host.  This tells B<krsh> not to run
B<kftgt>.  This option essentially makes B<krsh> function exactly like
B<rsh>.

=item B<-h>, B<--help>

Print a summary of options and exit.

=item B<-l> I<username>, B<--login>=I<username>

Set the username on the remote system to I<username>.  This is the user to
log in as as well as user to which to forward tickets.  If this option is
not given, the default will be the username on the local host.  This
option will often be necessary if the local username differs from the
Kerberos principal name, since B<kftgt> and B<rsh> differ on the default
otherwise.

=item B<-q>, B<--quiet>

Tell B<kftgt> to not print out its initial message about forwarding your
ticket.  Some programs that use rsh are confused by the initial output.

=item B<-V>, B<--verbose>

Print out each command and the arguments used before it's executed.

=item B<-v>, B<--version>

Print the version number of B<klogin> and exit.

=item B<-x>, B<--encrypt>

Encrypt the connection to the remote host.  This is not the default
because it only works with Kerberos v5 rsh.  If the remote system does not
have a Kerberos v5 keytab, or if you do not have a Kerberos v5 TGT, this
will cause B<krsh> to fail.

=back

=head1 BUGS

Regular rsh options are not accepted and passed to rsh.

=head1 SEE ALSO

aklog(1), kdestroy(1), kftgt(1), klogin(1), rsh(1), unlog(1)

=head1 AUTHORS

B<krsh> was originally written by Larry Schwimmer <opusl@stanford.edu>.

Questions and bug reports may be sent to Russ Allbery <rra@stanford.edu>,
but please be aware that we only support Stanford affiliates and may not be
able to help with problems at other sites.

=head1 LICENSE

Copyright 1996, 2001, 2002, 2003 Board of Trustees, Leland Stanford
Jr. University

All rights reserved.

Export of this software from the United States of America may require a
specific license from the United States Government.  It is the
responsibility of any person or organization contemplating export to obtain
such a license before exporting.

WITHIN THAT CONSTRAINT, permission to use, copy, modify, and distribute this
software and its documentation for any purpose and without fee is hereby
granted, provided that the above copyright notice appear in all copies and
that both that copyright notice and this permission notice appear in
supporting documentation, and that the name of Stanford University not be
used in advertising or publicity pertaining to distribution of the software
without specific, written prior permission.  Stanford University makes no
representations about the suitability of this software for any purpose.  It
is provided "as is" without express or implied warranty.

THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

=cut
