#!/usr/bin/perl -w
#
# Tests for basic k5start functionality.
#
# Written by Russ Allbery <eagle@eyrie.org>
# Copyright 2008, 2009
#     The Board of Trustees of the Leland Stanford Junior University
#
# See LICENSE for licensing terms.

use Test::More;

# The full path to the newly-built k5start client.
our $K5START = "$ENV{BUILD}/../k5start";

# The path to our data directory, which contains the keytab to use to test.
our $DATA = "$ENV{BUILD}/data";

# Load our test utility programs.
require "$ENV{SOURCE}/libtest.pl";

# Decide whether we have the configuration to run the tests.
if (-f "$DATA/test.keytab" and -f "$DATA/test.principal") {
    plan tests => 89;
} else {
    plan skip_all => 'no keytab configuration';
    exit 0;
}

# Get the test principal.
my $principal = contents ("$DATA/test.principal");

# Don't overwrite the user's ticket cache.
$ENV{KRB5CCNAME} = 'krb5cc_test';

# Basic authentication test.
unlink 'krb5cc_test';
my ($out, $err, $status)
    = command ($K5START, '-f', "$DATA/test.keytab", $principal);
is ($status, 0, 'Basic k5start command succeeds');
is ($err, '', ' with no errors');
like ($out, qr/^Kerberos initialization for \Q$principal\E(\@\S+)?\n\z/,
      ' and the right output');
my ($default, $service) = klist ();
like ($default, qr/^\Q$principal\E(\@\S+)?\z/, ' for the right principal');
like ($service, qr%^krbtgt/%, ' and the right service');

# Specify the full principal with -u.
unlink 'krb5cc_test';
($out, $err, $status)
    = command ($K5START, '-u', $principal, '-f', "$DATA/test.keytab");
is ($status, 0, 'k5start -u succeeds');
is ($err, '', ' with no errors');
like ($out, qr/^Kerberos initialization for \Q$principal\E(\@\S+)?\n\z/,
      ' and the right output');
($default, $service) = klist ();
like ($default, qr/^\Q$principal\E(\@\S+)?\z/, ' for the right principal');
like ($service, qr%^krbtgt/%, ' and the right service');

# If we have a principal with an instance, try -u and -i.
my ($name, $inst) = ($principal =~ m%^([^/\@]+)(?:/([^\@]+))%);
SKIP: {
    skip 'test principal has no instance', 5 unless $inst;
    unlink 'krb5cc_test';
    ($out, $err, $status) = command ($K5START, '-u', $name, '-i', $inst, '-f',
                                     "$DATA/test.keytab");
    is ($status, 0, 'k5start -u -i succeeds');
    is ($err, '', ' with no errors');
    like ($out,
          qr/^Kerberos initialization for \Q$principal\E(\@\S+)?\n\z/,
          ' and the right output');
    ($default, $service) = klist ();
    like ($default, qr/^\Q$principal\E(\@\S+)?\z/,
          ' for the right principal');
    like ($service, qr%^krbtgt/%, ' and the right service');
}

# Now with -U it should figure out the principal itself.
unlink 'krb5cc_test';
($out, $err, $status) = command ($K5START, '-Uf', "$DATA/test.keytab");
is ($status, 0, 'k5start -U succeeds');
is ($err, '', ' with no errors');
like ($out, qr/^Kerberos initialization for \Q$principal\E(\@\S+)?\n\z/,
      ' and the right output');
($default, $service) = klist ();
like ($default, qr/^\Q$principal\E(\@\S+)?\z/, ' for the right principal');
like ($service, qr%^krbtgt/%, ' and the right service');

# Test quiet and an explicit ticket cache.
unlink 'krb5cc_test';
($out, $err, $status)
    = command ($K5START, '-k', 'krb5cc_test2', '-qUf', "$DATA/test.keytab");
is ($status, 0, 'k5start -k -q succeeds');
is ($err, '', ' with no errors');
is ($out, '', ' and no output');
($default, $service) = klist ();
is ($default, undef, ' and the normal ticket cache is untouched');
$ENV{KRB5CCNAME} = 'krb5cc_test2';
($default, $service) = klist ();
like ($default, qr/^\Q$principal\E(\@\S+)?\z/,
      ' but the other has the right principal');
like ($service, qr%^krbtgt/%, ' and the right service');
unlink 'krb5cc_test2';
$ENV{KRB5CCNAME} = 'krb5cc_test';

# Test lifetime.  Hopefully even a test principal can get a five minute
# ticket lifetime.  We don't bother to try to parse klist output to figure
# out the lifetime, but instead check it using the -H option.
unlink 'krb5cc_test';
($out, $err, $status)
    = command ($K5START, '-l', '5m', '-qUf', "$DATA/test.keytab");
is ($status, 0, 'k5start -l 5m succeeds');
is ($err, '', ' with no errors');
is ($out, '', ' and no output');
($default, $service) = klist ();
like ($default, qr/^\Q$principal\E(\@\S+)?\z/, ' and the right principal');
like ($service, qr%^krbtgt/%, ' and the right service');
($out, $err, $status)
    = command ($K5START, '-H', '4', '-f', '/nonexistent', $principal);
is ($status, 0, ' and k5start -H succeeds without reauthenticating');
is ($err, '', ' with no errors');
is ($out, '', ' and no output');
($out, $err, $status)
    = command ($K5START, '-H', '10', '-f', '/nonexistent', $principal);
is ($status, 1, ' but fails if we need a 10 minute ticket');
like ($err, qr/^k5start: error getting credentials: /,
      ' with the right error');
is ($out, '', ' and no output');

# Test obtaining new tickets with -H.
($out, $err, $status)
    = command ($K5START, '-qH', '10', '-Uf', "$DATA/test.keytab");
is ($status, 0, 'k5start -H succeeds with new tickets');
is ($err, '', ' with no errors');
is ($out, '', ' and no output');
($default, $service) = klist ();
like ($default, qr/^\Q$principal\E(\@\S+)?\z/,
      ' and the right principal');
like ($service, qr%^krbtgt/%, ' and the right service');
($out, $err, $status)
    = command ($K5START, '-H', '10', '-f', '/nonexistent', $principal);
is ($status, 0, ' and k5start -H 10 succeeds without reauthenticating');
is ($err, '', ' with no errors');
is ($out, '', ' and no output');

# Attempt to authenticate as some other principal.  This should fail even
# though we have a valid ticket, since we don't have a valid ticket for the
# principal we want.
($out, $err, $status) = command ($K5START, '-H', 10, '-f', '/nonexistent',
                                 'bogus@EXAMPLE.COM');
is ($status, 1, ' and k5start -H 10 bogus tries to reauthenticate');
like ($err, qr/^k5start: error getting credentials: /, ' with correct error');
is ($out, '', ' and no output');

# Get a ticket for ourselves rather than a krbtgt and test verbose.  We
# need an instance here or we get weird results due to the defaults if -I
# isn't provided.
SKIP: {
    skip 'test principal has no instance', 8 unless $inst;
    unlink 'krb5cc_test';
    ($out, $err, $status) = command ($K5START, '-S', $name, '-I', $inst,
                                     '-vUf', "$DATA/test.keytab");
    is ($status, 0, 'k5start -S -I succeeds');
    is ($err, '', ' with no errors');
    my $short = $principal;
    $short =~ s/\@\S+$//;
    like ($out, qr/^Kerberos\ initialization\ for\ \Q$principal\E(\@\S+)?
                   \ for\ service\ \Q$short\E(\@\S+)?\n
                   k5start:\ authenticating\ as\ \Q$principal\E(\@\S+)?\n
                   k5start:\ getting\ tickets\ for
                   \ \Q$principal\E(\@\S+)?\n\z/x,
          ' and the right output');
    ($default, $service) = klist ();
    like ($default, qr/^\Q$principal\E(\@\S+)?\z/,
          ' for the right principal');
    like ($service, qr/^\Q$principal\E(\@\S+)?\z/, ' and the right service');

    # We now don't have a krbtgt ticket, so a regular k5start -H will attempt
    # to reauthenticate.
    ($out, $err, $status) = command ($K5START, '-H', 10, '-f', '/nonexistent',
                                     $principal);
    is ($status, 1, ' and k5start -H 10 tries to reauthenticate');
    like ($err, qr/^k5start: error getting credentials: /,
          ' with correct error');
    is ($out, '', ' and no output');
}

# Test running a command without the principal.  klist may fail if we have
# no K4 tickets since we're not giving the -5 option; allow for that
# (we'll catch real failures in the regex match on the output).
unlink 'krb5cc_test';
($out, $err, $status)
    = command ($K5START, '-qUf', "$DATA/test.keytab", 'klist');
ok ($status == 0 || $status == 1, 'k5start with command succeeds');
ok ($err eq '' || $err eq "klist: You have no tickets cached\n",
    ' with no or expected errors');
like ($out, qr,^(Credentials|Ticket)\ cache:
               \ (FILE:)?/tmp/krb5cc_\d+_\S{6}\n
               \s*(Default\ )?[Pp]rincipal:\ \Q$principal\E(\@\S+)?\n,xm,
      ' and the right output');
ok (!-f 'krb5cc_test', ' and the default cache file was not created');
my ($cache) = ($out =~ /cache: (?:FILE:)?(\S+)/);
ok (!$cache || !-f $cache, ' and the new cache file was deleted');

# Test running a command without the principal prefixed by --.
unlink 'krb5cc_test';
($out, $err, $status)
    = command ($K5START, '-qUf', "$DATA/test.keytab", '--', 'klist', '-5');
is ($status, 0, 'k5start with command and -- succeeds');
is ($err, '', ' with no errors');
like ($out, qr,^(Credentials|Ticket)\ cache:
               \ (FILE:)?/tmp/krb5cc_\d+_\S{6}\n
               \s*(Default\ )?[Pp]rincipal:\ \Q$principal\E(\@\S+)?\n,xm,
      ' and the right output');
ok (!-f 'krb5cc_test', ' and the default cache file was not created');
($cache) = ($out =~ /cache: (?:FILE:)?(\S+)/);
ok (!$cache || !-f $cache, ' and the new cache file was deleted');

# Test running a command with the principal.
unlink 'krb5cc_test';
($out, $err, $status)
    = command ($K5START, '-qf', "$DATA/test.keytab", $principal, 'klist');
ok ($status == 0 || $status == 1,
    'k5start with command and principal succeeds');
ok ($err eq '' || $err eq "klist: You have no tickets cached\n",
    ' with no or expected errors');
like ($out, qr,^(Credentials|Ticket)\ cache:
               \ (FILE:)?/tmp/krb5cc_\d+_\S{6}\n
               \s*(Default\ )?[Pp]rincipal:\ \Q$principal\E(\@\S+)?\n,xm,
      ' and the right output');
ok (!-f 'krb5cc_test', ' and the default cache file was not created');
($cache) = ($out =~ /cache: (?:FILE:)?(\S+)/);
ok (!$cache || !-f $cache, ' and the new cache file was deleted');

# Test running a command with the principal and a command prefixed by --.
unlink 'krb5cc_test';
($out, $err, $status)
    = command ($K5START, '-qf', "$DATA/test.keytab", $principal, '--',
               'klist', '-5');
is ($status, 0, 'k5start with command, principal, and -- succeeds');
is ($err, '', ' with no errors');
like ($out, qr,^(Credentials|Ticket)\ cache:
               \ (FILE:)?/tmp/krb5cc_\d+_\S{6}\n
               \s*(Default\ )?[Pp]rincipal:\ \Q$principal\E(\@\S+)?\n,xm,
      ' and the right output');
ok (!-f 'krb5cc_test', ' and the default cache file was not created');
($cache) = ($out =~ /cache: (?:FILE:)?(\S+)/);
ok (!$cache || !-f $cache, ' and the new cache file was deleted');

# Test running a command with the principal specified with -u.
unlink 'krb5cc_test';
($out, $err, $status)
    = command ($K5START, '-u', $principal, '-qf', "$DATA/test.keytab",
               'klist');
ok ($status == 0 || $status == 1, 'k5start with command and -u succeeds');
ok ($err eq '' || $err eq "klist: You have no tickets cached\n",
    ' with no or expected errors');
like ($out, qr,^(Credentials|Ticket)\ cache:
               \ (FILE:)?/tmp/krb5cc_\d+_\S{6}\n
               \s*(Default\ )?[Pp]rincipal:\ \Q$principal\E(\@\S+)?\n,xm,
      ' and the right output');
ok (!-f 'krb5cc_test', ' and the default cache file was not created');
($cache) = ($out =~ /cache: (?:FILE:)?(\S+)/);
ok (!$cache || !-f $cache, ' and the new cache file was deleted');

# Test running a command with the principal specified with -u and --
# before the command.
unlink 'krb5cc_test';
($out, $err, $status)
    = command ($K5START, '-u', $principal, '-qf', "$DATA/test.keytab",
               '--', 'klist', '-5');
is ($status, 0, 'k5start with command, -u, and -- succeeds');
is ($err, '', ' with no errors');
like ($out, qr,^(Credentials|Ticket)\ cache:
               \ (FILE:)?/tmp/krb5cc_\d+_\S{6}\n
               \s*(Default\ )?[Pp]rincipal:\ \Q$principal\E(\@\S+)?\n,xm,
      ' and the right output');
ok (!-f 'krb5cc_test', ' and the default cache file was not created');
($cache) = ($out =~ /cache: (?:FILE:)?(\S+)/);
ok (!$cache || !-f $cache, ' and the new cache file was deleted');

# Test propagation of exit status from a command.
unlink 'krb5cc_test';
($out, $err, $status)
    = command ($K5START, '-Uqf', "$DATA/test.keytab", '--', 'sh', '-c',
               'exit 3');
is ($status, 3, 'k5start of exit 3 returns correct exit status');
is ($err, '', ' with no errors');
ok (!-f 'krb5cc_test', ' and the default cache file was not created');

# Clean up.
unlink 'krb5cc_test';
