diff options
| author | Russ Allbery <rra@stanford.edu> | 2010-02-18 21:31:10 -0800 | 
|---|---|---|
| committer | Russ Allbery <rra@stanford.edu> | 2010-02-18 21:31:10 -0800 | 
| commit | a24d3ac3c7e8cb68fe2268f337a4edb599d5f881 (patch) | |
| tree | d8666db4e54a4ebd1ae69ddfcc37d6ffb9a18e31 /perl | |
| parent | 748170660e3a7b1db4320ba9b0144da2e252cd27 (diff) | |
Support unchanging keytabs with Heimdal without remctl
Heimdal supports retrieving a keytab containing the existing keys over
the kadmin protocol.  Move the support for using remctl to retrieve an
existing keytab into Wallet::Kadmin::MIT and provide two separate
methods in the Wallet::Kadmin interface: one which rekeys and one which
doesn't.  Implement the non-rekeying interface for Heimdal.  Expand the
test suite for the unchanging keytabs to include tests for the Heimdal
method.
Diffstat (limited to 'perl')
| -rw-r--r-- | perl/Wallet/Config.pm | 21 | ||||
| -rw-r--r-- | perl/Wallet/Kadmin.pm | 43 | ||||
| -rw-r--r-- | perl/Wallet/Kadmin/Heimdal.pm | 74 | ||||
| -rw-r--r-- | perl/Wallet/Kadmin/MIT.pm | 68 | ||||
| -rw-r--r-- | perl/Wallet/Object/Keytab.pm | 49 | ||||
| -rwxr-xr-x | perl/t/kadmin.t | 4 | ||||
| -rwxr-xr-x | perl/t/keytab.t | 127 | 
7 files changed, 257 insertions, 129 deletions
| diff --git a/perl/Wallet/Config.pm b/perl/Wallet/Config.pm index c59d3e3..396bf7d 100644 --- a/perl/Wallet/Config.pm +++ b/perl/Wallet/Config.pm @@ -26,7 +26,8 @@ Wallet::Config - Configuration handling for the wallet server  =for stopwords  DBI DSN SQLite subdirectories KEYTAB keytab kadmind KDC add-ons kadmin DNS  SRV kadmin keytabs remctl backend lowercased NETDB ACL NetDB unscoped -usernames rekey hostnames Allbery wallet-backend keytab-backend +usernames rekey hostnames Allbery wallet-backend keytab-backend Heimdal +rekeys  =head1 SYNOPSIS @@ -313,11 +314,19 @@ our $KEYTAB_TMP;  =head2 Retrieving Existing Keytabs -The keytab object backend optionally supports retrieving existing keys, -and hence keytabs, for Kerberos principals by contacting the KDC via -remctl and talking to B<keytab-backend>.  This is enabled by setting the -C<unchanging> flag on keytab objects.  To configure that support, set the -following variables. +Heimdal provides the choice, over the network protocol, of either +downloading the existing keys for a principal or generating new random +keys.  MIT Kerberos does not; downloading a keytab over the kadmin +protocol always rekeys the principal. + +For MIT Kerberos, the keytab object backend therefore optionally supports +retrieving existing keys, and hence keytabs, for Kerberos principals by +contacting the KDC via remctl and talking to B<keytab-backend>.  This is +enabled by setting the C<unchanging> flag on keytab objects.  To configure +that support, set the following variables. + +This is not required for Heimdal; for Heimdal, setting the C<unchanging> +flag is all that's needed.  =over 4 diff --git a/perl/Wallet/Kadmin.pm b/perl/Wallet/Kadmin.pm index 3ca531e..f3c2895 100644 --- a/perl/Wallet/Kadmin.pm +++ b/perl/Wallet/Kadmin.pm @@ -83,10 +83,12 @@ Wallet::Kadmin - Kerberos administration API for wallet keytab backend  =head1 SYNOPSIS      my $kadmin = Wallet::Kadmin->new; -    $kadmin->create ("host/foo.example.com"); -    $kadmin->keytab ("host/foo.example.com", "aes256-cts-hmac-sha1-96"); -    my $exists = $kadmin->exists ("host/oldshell.example.com"); -    $kadmin->destroy ("host/oldshell.example.com") if $exists; +    $kadmin->create ('host/foo.example.com'); +    $kadmin->keytab_rekey ('host/foo.example.com', 'keytab', +                           'aes256-cts-hmac-sha1-96'); +    my $data = $kadmin->keytab ('host/foo.example.com'); +    my $exists = $kadmin->exists ('host/oldshell.example.com'); +    $kadmin->destroy ('host/oldshell.example.com') if $exists;  =head1 DESCRIPTION @@ -162,19 +164,26 @@ kadmin command-line client, the sub CALLBACK will be called in the child  process before running the program.  This can be used to, for example,  properly clean up shared database handles. -=item keytab(PRINCIPAL, FILE [, ENCTYPE ... ]) - -A keytab is an on-disk store for the key or keys for a Kerberos principal. -Keytabs are used by services to verify incoming authentication from -clients or by automated processes that need to authenticate to Kerberos. -To create a keytab, the principal has to be created in Kerberos and then a -keytab is generated and stored in a file on disk. - -ktadd() creates a new keytab for the given principal, storing it in the -given file and limited to the enctypes supplied.  The enctype values must -be enctype strings recognized by the Kerberos implementation (strings like -C<aes256-cts-hmac-sha1-96> or C<des-cbc-crc>).  Returns true on success -and false on failure. +=item keytab(PRINCIPAL) + +keytab() creates a keytab for the given principal, storing it in the given +file.  A keytab is an on-disk store for the key or keys for a Kerberos +principal.  Keytabs are used by services to verify incoming authentication +from clients or by automated processes that need to authenticate to +Kerberos.  To create a keytab, the principal has to have previously been +created in the Kerberos KDC.  Returns the keytab as binary data on success +and undef on failure. + +=item keytab_rekey(PRINCIPAL, FILE [, ENCTYPE ...]) + +Like keytab(), but randomizes the key for the principal before generating +the keytab and writes it to the given file.  This will invalidate any +existing keytabs for that principal.  This method can also limit the +encryption types of the keys for that principal via the optional ENCTYPE +arguments.  The enctype values must be enctype strings recognized by the +Kerberos implementation (strings like C<aes256-cts-hmac-sha1-96> or +C<des-cbc-crc>).  If none are given, the KDC defaults will be used. +Returns true on success and false on failure.  =back diff --git a/perl/Wallet/Kadmin/Heimdal.pm b/perl/Wallet/Kadmin/Heimdal.pm index 0ac8cd9..e066006 100644 --- a/perl/Wallet/Kadmin/Heimdal.pm +++ b/perl/Wallet/Kadmin/Heimdal.pm @@ -39,6 +39,23 @@ sub canonicalize_principal {      return $principal;  } +# Read the entirety of a possibly binary file and return the contents.  If +# reading the file fails, set the error message and return undef. +sub slurp_file { +    my ($self, $file) = @_; +    unless (open (TMPFILE, '<', $file)) { +        $self->error ("cannot open temporary file $file: $!"); +        return; +    } +    local $/; +    my $data = <TMPFILE>; +    unless (close TMPFILE) { +        $self->error ("cannot read temporary file $file: $!"); +        return; +    } +    return $data; +} +  ##############################################################################  # Public interfaces  ############################################################################## @@ -93,11 +110,38 @@ sub create {      return 1;  } -# Create a keytab from a principal.  Takes the principal, the file, and -# optionally a list of encryption types to which to limit the keytab.  Return -# true if successful, false otherwise.  If the keytab creation fails, sets the -# error. +# Create a keytab for a principal.  Returns the keytab as binary data or undef +# on failure, setting the error.  sub keytab { +    my ($self, $principal) = @_; +    $principal = $self->canonicalize_principal ($principal); +    my $kadmin = $self->{client}; +    my $file = $Wallet::Config::KEYTAB_TMP . "/keytab.$$"; +    unlink $file; +    my $princdata = eval { $kadmin->getPrincipal ($principal) }; +    if ($@) { +        $self->error ("error creating keytab for $principal: $@"); +        return; +    } elsif (!$princdata) { +        $self->error ("error creating keytab for $principal: principal does" +                      . " not exist"); +        return; +    } +    eval { $kadmin->extractKeytab ($princdata, $file) }; +    if ($@) { +        $self->error ("error creating keytab for principal: $@"); +        return; +    } +    my $data = $self->slurp_file ($file); +    unlink $file; +    return $data; +} + +# Create a keytab for a principal, randomizing the keys for that principal at +# the same time.  Takes the principal, the file, and optionally a list of +# encryption types to which to limit the keytab.  Return true if successful, +# false otherwise.  If the keytab creation fails, sets the error. +sub keytab_rekey {      my ($self, $principal, $file, @enctypes) = @_;      $principal = $self->canonicalize_principal ($principal); @@ -213,10 +257,12 @@ Wallet::Kadmin::Heimdal - Wallet Kerberos administration API for Heimdal  =head1 SYNOPSIS      my $kadmin = Wallet::Kadmin::Heimdal->new; -    $kadmin->create ("host/foo.example.com"); -    $kadmin->keytab ("host/foo.example.com", "aes256-cts-hmac-sha1-96"); -    my $exists = $kadmin->exists ("host/oldshell.example.com"); -    $kadmin->destroy ("host/oldshell.example.com") if $exists; +    $kadmin->create ('host/foo.example.com'); +    $kadmin->keytab_rekey ('host/foo.example.com', 'keytab', +                           'aes256-cts-hmac-sha1-96'); +    my $data = $kadmin->keytab ('host/foo.example.com'); +    my $exists = $kadmin->exists ('host/oldshell.example.com'); +    $kadmin->destroy ('host/oldshell.example.com') if $exists;  =head1 DESCRIPTION @@ -228,6 +274,18 @@ To use this object, several configuration parameters must be set.  See  Wallet::Config(3) for details on those configuration parameters and  information about how to set wallet configuration. +=head1 FILES + +=over 4 + +=item KEYTAB_TMP/keytab.<pid> + +The keytab is created in this file and then read into memory.  KEYTAB_TMP +is set in the wallet configuration, and <pid> is the process ID of the +current process.  The file is unlinked after being read. + +=back +  =head1 SEE ALSO  kadmin(8), Wallet::Config(3), Wallet::Kadmin(3), diff --git a/perl/Wallet/Kadmin/MIT.pm b/perl/Wallet/Kadmin/MIT.pm index 9ab575c..1c6d2c1 100644 --- a/perl/Wallet/Kadmin/MIT.pm +++ b/perl/Wallet/Kadmin/MIT.pm @@ -137,11 +137,52 @@ sub create {      return 1;  } -# Create a keytab from a principal.  Takes the principal, the file, and -# optionally a list of encryption types to which to limit the keytab.  Return -# true if successful, false otherwise.  If the keytab creation fails, sets the -# error. +# Retrieve an existing keytab from the KDC via a remctl call.  The KDC needs +# to be running the keytab-backend script and support the keytab retrieve +# remctl command.  In addition, the user must have configured us with the path +# to a ticket cache and the host to which to connect with remctl.  Returns the +# keytab on success and undef on failure.  sub keytab { +    my ($self, $principal) = @_; +    my $host = $Wallet::Config::KEYTAB_REMCTL_HOST; +    unless ($host and $Wallet::Config::KEYTAB_REMCTL_CACHE) { +        $self->error ('keytab unchanging support not configured'); +        return; +    } +    eval { require Net::Remctl }; +    if ($@) { +        $self->error ("keytab unchanging support not available: $@"); +        return; +    } +    if ($principal !~ /\@/ && $Wallet::Config::KEYTAB_REALM) { +        $principal .= '@' . $Wallet::Config::KEYTAB_REALM; +    } +    local $ENV{KRB5CCNAME} = $Wallet::Config::KEYTAB_REMCTL_CACHE; +    my $port = $Wallet::Config::KEYTAB_REMCTL_PORT || 0; +    my $remctl_princ = $Wallet::Config::KEYTAB_REMCTL_PRINCIPAL || ''; +    my @command = ('keytab', 'retrieve', $principal); +    my $result = Net::Remctl::remctl ($host, $port, $remctl_princ, @command); +    if ($result->error) { +        $self->error ("cannot retrieve keytab for $principal: ", +                      $result->error); +        return; +    } elsif ($result->status != 0) { +        my $error = $result->stderr; +        $error =~ s/\s+$//; +        $error =~ s/\n/ /g; +        $self->error ("cannot retrieve keytab for $principal: $error"); +        return; +    } else { +        return $result->stdout; +    } +} + +# Create a keytab for a principal, randomizing the keys for that principal +# in the process.  Takes the principal, the file, and optionally a list of +# encryption types to which to limit the keytab.  Return true if +# successful, false otherwise.  If the keytab creation fails, sets the +# error. +sub keytab_rekey {      my ($self, $principal, $file, @enctypes) = @_;      unless ($self->valid_principal ($principal)) {          $self->error ("invalid principal name: $principal"); @@ -210,7 +251,7 @@ __END__  ##############################################################################  =for stopwords -keytabs keytab kadmin KDC API Allbery +rekeying rekeys remctl backend keytabs keytab kadmin KDC API Allbery  =head1 NAME @@ -219,10 +260,12 @@ Wallet::Kadmin::MIT - Wallet Kerberos administration API for MIT  =head1 SYNOPSIS      my $kadmin = Wallet::Kadmin::MIT->new; -    $kadmin->create ("host/foo.example.com"); -    $kadmin->keytab ("host/foo.example.com", "aes256-cts-hmac-sha1-96"); -    my $exists = $kadmin->exists ("host/oldshell.example.com"); -    $kadmin->destroy ("host/oldshell.example.com") if $exists; +    $kadmin->create ('host/foo.example.com'); +    $kadmin->keytab_rekey ('host/foo.example.com', 'keytab', +                           'aes256-cts-hmac-sha1-96'); +    my $data = $kadmin->keytab ('host/foo.example.com'); +    my $exists = $kadmin->exists ('host/oldshell.example.com'); +    $kadmin->destroy ('host/oldshell.example.com') if $exists;  =head1 DESCRIPTION @@ -231,6 +274,13 @@ providing an interface to create and delete principals and create keytabs.  It provides the API documented in Wallet::Kadmin(3) for an MIT Kerberos  KDC. +MIT Kerberos does not provide any method via the kadmin network protocol +to retrieve a keytab for a principal without rekeying it, so the keytab() +method (as opposed to keytab_rekey(), which rekeys the principal) is +implemented using a remctl backend.  For that method (used for unchanging +keytab objects) to work, the necessary wallet configuration and remctl +interface on the KDC must be set up. +  To use this object, several configuration parameters must be set.  See  Wallet::Config(3) for details on those configuration parameters and  information about how to set wallet configuration. diff --git a/perl/Wallet/Object/Keytab.pm b/perl/Wallet/Object/Keytab.pm index 44ee003..5c66967 100644 --- a/perl/Wallet/Object/Keytab.pm +++ b/perl/Wallet/Object/Keytab.pm @@ -180,49 +180,6 @@ sub sync_list {  }  ############################################################################## -# Keytab retrieval -############################################################################## - -# Retrieve an existing keytab from the KDC via a remctl call.  The KDC needs -# to be running the keytab-backend script and support the keytab retrieve -# remctl command.  In addition, the user must have configured us with the path -# to a ticket cache and the host to which to connect with remctl.  Returns the -# keytab on success and undef on failure. -sub keytab_retrieve { -    my ($self, $keytab) = @_; -    my $host = $Wallet::Config::KEYTAB_REMCTL_HOST; -    unless ($host and $Wallet::Config::KEYTAB_REMCTL_CACHE) { -        $self->error ('keytab unchanging support not configured'); -        return; -    } -    eval { require Net::Remctl }; -    if ($@) { -        $self->error ("keytab unchanging support not available: $@"); -        return; -    } -    if ($Wallet::Config::KEYTAB_REALM) { -        $keytab .= '@' . $Wallet::Config::KEYTAB_REALM; -    } -    local $ENV{KRB5CCNAME} = $Wallet::Config::KEYTAB_REMCTL_CACHE; -    my $port = $Wallet::Config::KEYTAB_REMCTL_PORT || 0; -    my $principal = $Wallet::Config::KEYTAB_REMCTL_PRINCIPAL || ''; -    my @command = ('keytab', 'retrieve', $keytab); -    my $result = Net::Remctl::remctl ($host, $port, $principal, @command); -    if ($result->error) { -        $self->error ("cannot retrieve keytab for $keytab: ", $result->error); -        return; -    } elsif ($result->status != 0) { -        my $error = $result->stderr; -        $error =~ s/\s+$//; -        $error =~ s/\n/ /g; -        $self->error ("cannot retrieve keytab for $keytab: $error"); -        return; -    } else { -        return $result->stdout; -    } -} - -##############################################################################  # Core methods  ############################################################################## @@ -365,8 +322,9 @@ sub get {          $self->error ("cannot get $id: object is locked");          return;      } +    my $kadmin = $self->{kadmin};      if ($self->flag_check ('unchanging')) { -        my $result = $self->keytab_retrieve ($self->{name}); +        my $result = $kadmin->keytab ($self->{name});          if (defined $result) {              $self->log_action ('get', $user, $host, $time);          } @@ -379,8 +337,7 @@ sub get {      my $file = $Wallet::Config::KEYTAB_TMP . "/keytab.$$";      unlink $file;      my @enctypes = $self->attr ('enctypes'); -    my $kadmin = $self->{kadmin}; -    if (not $kadmin->keytab ($self->{name}, $file, @enctypes)) { +    if (not $kadmin->keytab_rekey ($self->{name}, $file, @enctypes)) {          $self->error ($kadmin->error);          return;      } diff --git a/perl/t/kadmin.t b/perl/t/kadmin.t index 9c49995..a29cae3 100755 --- a/perl/t/kadmin.t +++ b/perl/t/kadmin.t @@ -91,7 +91,7 @@ SKIP: {      is ($kadmin->create ('wallet/one'), 1, 'Creating wallet/one works');      is ($kadmin->exists ('wallet/one'), 1, ' and it now exists');      unlink ('./tmp.keytab'); -    is ($kadmin->keytab ('wallet/one', './tmp.keytab'), 1, +    is ($kadmin->keytab_rekey ('wallet/one', './tmp.keytab'), 1,          ' and retrieving a keytab works');      ok (-s './tmp.keytab', ' and the resulting keytab is non-zero');      is (getcreds ('./tmp.keytab', "wallet/one\@$Wallet::Config::KEYTAB_REALM"), @@ -101,7 +101,7 @@ SKIP: {      # Delete the principal and confirm behavior.      is ($kadmin->destroy ('wallet/one'), 1, 'Deleting principal works');      is ($kadmin->exists ('wallet/one'), 0, ' and now it does not exist'); -    is ($kadmin->keytab ('wallet/one', './tmp.keytab'), undef, +    is ($kadmin->keytab_rekey ('wallet/one', './tmp.keytab'), undef,          ' and retrieving the keytab does not work');      ok (! -f './tmp.keytab', ' and no file was created');      like ($kadmin->error, qr%^error creating keytab for wallet/one%, diff --git a/perl/t/keytab.t b/perl/t/keytab.t index a14b63e..a702c0f 100755 --- a/perl/t/keytab.t +++ b/perl/t/keytab.t @@ -9,7 +9,7 @@  # See LICENSE for licensing terms.  use POSIX qw(strftime); -use Test::More tests => 125; +use Test::More tests => 135;  use Wallet::Admin;  use Wallet::Config; @@ -378,12 +378,7 @@ EOO  # Tests for unchanging support.  Skip these if we don't have a keytab or if we  # can't find remctld.  SKIP: { -    skip 'no keytab configuration', 17 unless -f 't/data/test.keytab'; -    my @path = (split (':', $ENV{PATH}), '/usr/local/sbin', '/usr/sbin'); -    my ($remctld) = grep { -x $_ } map { "$_/remctld" } @path; -    skip 'remctld not found', 17 unless $remctld; -    eval { require Net::Remctl }; -    skip 'Net::Remctl not available', 17 if $@; +    skip 'no keytab configuration', 27 unless -f 't/data/test.keytab';      # Set up our configuration.      $Wallet::Config::KEYTAB_FILE      = 't/data/test.keytab'; @@ -406,41 +401,85 @@ SKIP: {      ok (defined ($two), 'Creating wallet/two succeeds');      is ($two->flag_set ('unchanging', @trace), 1, ' and setting unchanging'); -    # Now spawn our remctld server and get a ticket cache. -    remctld_spawn ($remctld, $principal, 't/data/test.keytab', -                   't/data/keytab.conf'); -    $ENV{KRB5CCNAME} = 'krb5cc_test'; -    getcreds ('t/data/test.keytab', $principal); -    $ENV{KRB5CCNAME} = 'krb5cc_good'; +    # Finally we can test.  First the MIT Kerberos tests. +  SKIP: { +        skip 'skipping MIT unchanging tests for Heimdal', 12 +            if (lc ($Wallet::Config::KEYTAB_KRBTYPE) eq 'heimdal'); + +        # We need remctld and Net::Remctl. +        my @path = (split (':', $ENV{PATH}), '/usr/local/sbin', '/usr/sbin'); +        my ($remctld) = grep { -x $_ } map { "$_/remctld" } @path; +        skip 'remctld not found', 12 unless $remctld; +        eval { require Net::Remctl }; +        skip 'Net::Remctl not available', 12 if $@; + +        # Now spawn our remctld server and get a ticket cache. +        remctld_spawn ($remctld, $principal, 't/data/test.keytab', +                       't/data/keytab.conf'); +        $ENV{KRB5CCNAME} = 'krb5cc_test'; +        getcreds ('t/data/test.keytab', $principal); +        $ENV{KRB5CCNAME} = 'krb5cc_good'; + +        # Do the unchanging tests for MIT Kerberos. +        is ($one->get (@trace), undef, 'Get without configuration fails'); +        is ($one->error, 'keytab unchanging support not configured', +            ' with the right error'); +        $Wallet::Config::KEYTAB_REMCTL_CACHE = 'krb5cc_test'; +        is ($one->get (@trace), undef, ' and still fails without host'); +        is ($one->error, 'keytab unchanging support not configured', +            ' with the right error'); +        $Wallet::Config::KEYTAB_REMCTL_HOST = 'localhost'; +        $Wallet::Config::KEYTAB_REMCTL_PRINCIPAL = $principal; +        $Wallet::Config::KEYTAB_REMCTL_PORT = 14373; +        is ($one->get (@trace), undef, ' and still fails without ACL'); +        is ($one->error, +            "cannot retrieve keytab for wallet/one\@$realm: Access denied", +            ' with the right error'); +        open (ACL, '>', 'test-acl') or die "cannot create test-acl: $!\n"; +        print ACL "$principal\n"; +        close ACL; +        is ($one->get (@trace), 'Keytab for wallet/one', 'Now get works'); +        is ($ENV{KRB5CCNAME}, 'krb5cc_good', +            ' and we did not nuke the cache name'); +        is ($one->get (@trace), 'Keytab for wallet/one', +            ' and we get the same thing the second time'); +        is ($one->flag_clear ('unchanging', @trace), 1, +            'Clearing the unchanging flag works'); +        my $data = $object->get (@trace); +        ok (defined ($data), ' and getting the keytab works'); +        ok (valid ($data, 'wallet/one'), ' and the keytab is valid'); +        is ($two->get (@trace), undef, 'Get for wallet/two does not work'); +        is ($two->error, +            "cannot retrieve keytab for wallet/two\@$realm: bite me", +            ' with the right error'); +        is ($one->destroy (@trace), 1, 'Destroying wallet/one works'); +        is ($two->destroy (@trace), 1, ' as does destroying wallet/two'); +        remctld_stop; +    } -    # Finally we can test. -    is ($one->get (@trace), undef, 'Get without configuration fails'); -    is ($one->error, 'keytab unchanging support not configured', -        ' with the right error'); -    $Wallet::Config::KEYTAB_REMCTL_CACHE = 'krb5cc_test'; -    is ($one->get (@trace), undef, ' and still fails without host'); -    is ($one->error, 'keytab unchanging support not configured', -        ' with the right error'); -    $Wallet::Config::KEYTAB_REMCTL_HOST = 'localhost'; -    $Wallet::Config::KEYTAB_REMCTL_PRINCIPAL = $principal; -    $Wallet::Config::KEYTAB_REMCTL_PORT = 14373; -    is ($one->get (@trace), undef, ' and still fails without ACL'); -    is ($one->error, -        "cannot retrieve keytab for wallet/one\@$realm: Access denied", -        ' with the right error'); -    open (ACL, '>', 'test-acl') or die "cannot create test-acl: $!\n"; -    print ACL "$principal\n"; -    close ACL; -    is ($one->get (@trace), 'Keytab for wallet/one', 'Now get works'); -    is ($ENV{KRB5CCNAME}, 'krb5cc_good', -        ' and we did not nuke the cache name'); -    is ($two->get (@trace), undef, ' but get for wallet/two does not'); -    is ($two->error, -        "cannot retrieve keytab for wallet/two\@$realm: bite me", -        ' with the right error'); -    is ($one->destroy (@trace), 1, 'Destroying wallet/one works'); -    is ($two->destroy (@trace), 1, ' as does destroying wallet/two'); -    remctld_stop; +    # Now Heimdal.  Since the keytab contains timestamps, before testing for +    # equality we have to substitute out the timestamps. +  SKIP: { +        skip 'skipping Heimdal unchanging tests for MIT', 10 +            if (lc ($Wallet::Config::KEYTAB_KRBTYPE) eq 'mit'); +        my $data = $one->get (@trace); +        ok (defined $data, 'Get of unchanging keytab works'); +        ok (valid ($data, 'wallet/one'), ' and the keytab is valid'); +        my $second = $one->get (@trace); +        ok (defined $second, ' and second retrieval also works'); +        $data =~ s/one.{8}/one\000\000\000\000\000\000\000\000/g; +        $second =~ s/one.{8}/one\000\000\000\000\000\000\000\000/g; +        is ($data, $second, ' and the keytab matches'); +        is ($one->flag_clear ('unchanging', @trace), 1, +            'Clearing the unchanging flag works'); +        $data = $one->get (@trace); +        ok (defined ($data), ' and getting the keytab works'); +        ok (valid ($data, 'wallet/one'), ' and the keytab is valid'); +        $data =~ s/one.{8}/one\000\000\000\000\000\000\000\000/g; +        ok ($data ne $second, ' and the new keytab is different'); +        is ($one->destroy (@trace), 1, 'Destroying wallet/one works'); +        is ($two->destroy (@trace), 1, ' as does destroying wallet/two'); +    }      # Check that history has been updated correctly.      $history .= <<"EOO"; @@ -450,6 +489,12 @@ $date  set flag unchanging      by $user from $host  $date  get      by $user from $host +$date  get +    by $user from $host +$date  clear flag unchanging +    by $user from $host +$date  get +    by $user from $host  $date  destroy      by $user from $host  EOO | 
