summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRuss Allbery <rra@stanford.edu>2007-09-25 20:57:06 +0000
committerRuss Allbery <rra@stanford.edu>2007-09-25 20:57:06 +0000
commit766ba9295705be7c91593b6e7ce5db66bf88d453 (patch)
treee568ee88b65b3d1b592a74d32e34fd4f2f063847
parent3242b66fbf8274991d3fbb0d02ca85e1e2ca60b6 (diff)
Add support for synchronizing a key with an AFS kaserver in the keytab
object implementation, extracting the DES key with Authen::Krb5 (since ktutil doesn't work). Rename the KEYTAB_CACHE variable to KEYTAB_REMCTL_CACHE to match the rest of the keytab retrieval configuration and reorganize the Wallet::Config documentation to group related configuration options for the keytab backend. Fix a column name in the keytab_enctypes table to be more consistent with the rest of the schema.
-rw-r--r--TODO5
-rw-r--r--perl/Wallet/Config.pm107
-rw-r--r--perl/Wallet/Object/Keytab.pm285
-rw-r--r--perl/Wallet/Schema.pm26
-rw-r--r--perl/t/data/README17
-rwxr-xr-xperl/t/keytab.t121
-rwxr-xr-xperl/t/schema.t2
7 files changed, 528 insertions, 35 deletions
diff --git a/TODO b/TODO
index dcf5b90..e836f39 100644
--- a/TODO
+++ b/TODO
@@ -6,13 +6,8 @@ Required to replace leland_srvtab:
enctype attribute on the object and include the enctypes in the object
show display.
-* Add an attribute indicating that the principal should be synchronized
- with Kerberos v4 and include it in the object show display.
-
* Implement creation of srvtabs from keytabs in the wallet client.
-* Add kasetkey support to the keytab object backend.
-
* Write new files atomically in the wallet client and save backups unless
told not to (write to file.new, link the old file to file.old, and do
an atomic rename).
diff --git a/perl/Wallet/Config.pm b/perl/Wallet/Config.pm
index 9a1f9db..c52cfd1 100644
--- a/perl/Wallet/Config.pm
+++ b/perl/Wallet/Config.pm
@@ -170,17 +170,6 @@ client.
=over 4
-=item KEYTAB_CACHE
-
-Specifies the ticket cache to use when retrieving existing keytabs from the
-KDC. This is only used to implement support for the C<unchanging> flag.
-The ticket cache must be for a principal with access to run C<keytab
-retrieve> via remctl on KEYTAB_REMCTL_HOST.
-
-=cut
-
-our $KEYTAB_CACHE;
-
=item KEYTAB_FILE
Specifies the keytab to use to authenticate to B<kadmind>. The principal
@@ -256,6 +245,44 @@ default to the local realm.
our $KEYTAB_REALM;
+=item KEYTAB_TMP
+
+A directory into which the wallet can write keytabs temporarily while
+processing C<get> commands from clients. The keytabs are written into this
+directory with predictable names, so this should not be a system temporary
+directory such as F</tmp> or F</var/tmp>. It's best to create a directory
+solely for this purpose that's owned by the user the wallet server will run
+as.
+
+KEYTAB_TMP must be set to use keytab objects.
+
+=cut
+
+our $KEYTAB_TMP;
+
+=back
+
+=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.
+
+=over 4
+
+=item KEYTAB_REMCTL_CACHE
+
+Specifies the ticket cache to use when retrieving existing keytabs from the
+KDC. This is only used to implement support for the C<unchanging> flag.
+The ticket cache must be for a principal with access to run C<keytab
+retrieve> via remctl on KEYTAB_REMCTL_HOST.
+
+=cut
+
+our $KEYTAB_CACHE;
+
=item KEYTAB_REMCTL_HOST
The host to which to connect with remctl to retrieve existing keytabs. This
@@ -289,20 +316,60 @@ will be used.
our $KEYTAB_REMCTL_PORT;
-=item KEYTAB_TMP
+=back
-A directory into which the wallet can write keytabs temporarily while
-processing C<get> commands from clients. The keytabs are written into this
-directory with predictable names, so this should not be a system temporary
-directory such as F</tmp> or F</var/tmp>. It's best to create a directory
-solely for this purpose that's owned by the user the wallet server will run
-as.
+=head2 Synchronization with AFS kaserver
-KEYTAB_TMP must be set to use keytab objects.
+The keytab backend optionally supports synchronizing keys between the
+Kerberos v5 realm and a Kerberos v4 realm using kaserver. This
+synchronization is done using B<kasetkey> and is controlled by the C<sync>
+attribute on keytab objects. To configure that support, set the following
+variables.
+
+=over 4
+
+=item $KEYTAB_AFS_ADMIN
+
+The Kerberos v4 principal to use for authentication to the AFS kaserver. If
+this principal is not in the default local Kerberos v4 realm, it must be
+fully qualified. A srvtab for this principal must be stored in the path set
+in $KEYTAB_AFS_SRVTAB. This principal must have the ADMIN flag set in the
+AFS kaserver so that it can create and remove principals. This variable
+must be set to use the kaserver synchronization support.
=cut
-our $KEYTAB_TMP;
+our $KEYTAB_AFS_ADMIN;
+
+=item $KEYTAB_AFS_KASETKEY
+
+The path to the B<kasetkey> command-line client. The default value is
+C<kasetkey>, which will cause the wallet to search for B<kasetkey> on its
+default PATH.
+
+=cut
+
+our $KEYTAB_AFS_KASETKEY = 'kasetkey';
+
+=item $KEYTAB_AFS_REALM
+
+The name of the Kerberos v4 realm with which to synchronize keys. This is a
+realm, not a cell, so it should be in all uppercase. If this variable is
+not set, the default is the realm determined from the local cell name.
+
+=cut
+
+our $KEYTAB_AFS_REALM;
+
+=item $KEYTAB_AFS_SRVTAB
+
+The path to a srvtab used to authenticate to the AFS kaserver. This srvtab
+should be for the principal set in $KEYTAB_AFS_ADMIN. This variable must be
+set to use the kaserver synchronization support.
+
+=cut
+
+our $KEYTAB_AFS_SRVTAB;
=back
diff --git a/perl/Wallet/Object/Keytab.pm b/perl/Wallet/Object/Keytab.pm
index 582f78c..64e7ce1 100644
--- a/perl/Wallet/Object/Keytab.pm
+++ b/perl/Wallet/Object/Keytab.pm
@@ -172,6 +172,199 @@ sub kadmin_delprinc {
}
##############################################################################
+# AFS kaserver synchronization
+##############################################################################
+
+# Given a Kerberos v5 principal name, convert it to a Kerberos v4 principal
+# name. Returns undef if it can't convert the name for some reason (right
+# now, only if the principal has more than two parts). Note that this mapping
+# does not guarantee a unique result; multiple hosts in different domains can
+# be mapped to the same Kerberos v4 principal name using this function.
+sub kaserver_name {
+ my ($self, $k5) = @_;
+ my %host = map { $_ => 1 } qw(host ident imap pop smtp);
+ $k5 =~ s/\@.*//;
+ my @parts = split ('/', $k5);
+ if (@parts == 1) {
+ return $parts[0];
+ } elsif (@parts > 2) {
+ return undef;
+ } elsif ($host{$parts[0]}) {
+ $parts[1] =~ s/\..*//;
+ $parts[0] = 'rcmd' if $parts[0] eq 'host';
+ }
+ my $k4 = join ('.', @parts);
+ if ($Wallet::Config::KEYTAB_AFS_REALM) {
+ $k4 .= '@' . $Wallet::Config::KEYTAB_AFS_REALM;
+ }
+ return $k4;
+}
+
+# Given a keytab file name, the Kerberos v5 principal that's stored in that
+# keytab, a srvtab file name, and the corresponding Kerberos v4 principal,
+# write out a srvtab file containing the DES key in that keytab. Fails if
+# there is no DES key in the keytab.
+sub kaserver_srvtab {
+ my ($self, $keytab, $k5, $srvtab, $k4) = @_;
+
+ # Gah. Someday I will write Perl bindings for Kerberos that are less
+ # broken.
+ eval { require Authen::Krb5 };
+ if ($@) {
+ $self->error ("kaserver synchronization support not available: $@");
+ return undef;
+ }
+ eval { Authen::Krb5::init_context() };
+ if ($@ and not $@ =~ /^Authen::Krb5 already initialized/) {
+ $self->error ('Kerberos initialization failed');
+ return undef;
+ }
+ undef $@;
+
+ # Do the interface dance. We call kt_read_service_key with 0 for the kvno
+ # to get any kvno, which works with MIT Kerberos at least. Assume a DES
+ # enctype of 1. This code won't work with any enctype other than
+ # des-cbc-crc.
+ my $princ = Authen::Krb5::parse_name ($k5);
+ unless (defined $princ) {
+ my $error = Authen::Krb5::error();
+ $self->error ("cannot parse $k5: $error");
+ return undef;
+ }
+ my $key = Authen::Krb5::kt_read_service_key ($keytab, $princ, 0, 1);
+ unless (defined $key) {
+ my $error = Authen::Krb5::error();
+ $self->error ("cannot find des-cbc-crc key in $keytab: $error");
+ return undef;
+ }
+ unless (open (SRVTAB, '>', $srvtab)) {
+ $self->error ("cannot create $srvtab: $!");
+ return undef;
+ }
+
+ # srvtab format is nul-terminated name, nul-terminated instance,
+ # nul-terminated realm, single character kvno (which we always set to 0),
+ # and DES keyblock.
+ my ($principal, $realm) = split ('@', $k4);
+ my ($name, $inst) = split (/\./, $principal, 2);
+ my $data = join ("\0", $name, $inst, $realm);
+ $data .= "\0\0" . $key->contents;
+ print SRVTAB $data;
+ unless (close SRVTAB) {
+ unlink $srvtab;
+ $self->error ("cannot write to $srvtab: $!");
+ return undef;
+ }
+ return 1;
+}
+
+# Given a principal name and a path to the keytab, synchronizes the key with a
+# principal in an AFS kaserver. Returns true on success and false on failure.
+# On failure, sets the internal error.
+sub kaserver_sync {
+ my ($self, $principal, $keytab) = @_;
+ my $admin = $Wallet::Config::KEYTAB_AFS_ADMIN;
+ my $admin_srvtab = $Wallet::Config::KEYTAB_AFS_SRVTAB;
+ my $kasetkey = $Wallet::Config::KEYTAB_AFS_KASETKEY;
+ unless ($kasetkey and $admin and $admin_srvtab) {
+ $self->error ('kaserver synchronization not configured');
+ return undef;
+ }
+ if ($Wallet::Config::KEYTAB_REALM) {
+ $principal .= '@' . $Wallet::Config::KEYTAB_REALM;
+ }
+ my $k4 = $self->kaserver_name ($principal);
+ if (not defined $k4) {
+ $self->error ("cannot convert $principal to Kerberos v4");
+ return undef;
+ }
+ my $srvtab = $Wallet::Config::KEYTAB_TMP . "/srvtab.$$";
+ unless ($self->kaserver_srvtab ($keytab, $principal, $srvtab, $k4)) {
+ return undef;
+ }
+ my $pid = open (KASETKEY, '-|');
+ if (not defined $pid) {
+ $self->error ("cannot fork: $!");
+ unlink $srvtab;
+ return undef;
+ } elsif ($pid == 0) {
+ open (STDERR, '>&STDOUT') or die "cannot redirect stderr: $!\n";
+ exec ($kasetkey, '-k', $admin_srvtab, '-a', $admin, '-c', $srvtab,
+ '-s', $k4)
+ or die "cannot exec $kasetkey: $!\n";
+ } else {
+ local $/;
+ my $output = <KASETKEY>;
+ close KASETKEY;
+ if ($? != 0) {
+ $output =~ s/\s+\z//;
+ $output =~ s/\n/, /g;
+ $output = ': ' . $output if $output;
+ $self->error ("cannot synchronize key with kaserver$output");
+ unlink $srvtab;
+ return undef;
+ }
+ }
+ unlink $srvtab;
+ return 1;
+}
+
+# Set the kaserver sync attribute. Called by attr(). Returns true on success
+# and false on failure, setting the object error if it fails.
+sub kaserver_set {
+ my ($self, $user, $host, $time) = @_;
+ $time ||= time;
+ my @trace = ($user, $host, $time);
+ my $name = $self->{name};
+ eval {
+ my $sql = "select ks_name from keytab_sync where ks_name = ? and
+ ks_target = 'kaserver'";
+ my $result = $self->{dbh}->selectrow_array ($sql, undef, $name);
+ if ($result) {
+ die "kaserver synchronization already set\n";
+ }
+ $sql = "insert into keytab_sync (ks_name, ks_target)
+ values (?, 'kaserver')";
+ $self->{dbh}->do ($sql, undef, $name);
+ $self->log_set ('type_data sync', undef, 'kaserver', @trace);
+ $self->{dbh}->commit;
+ };
+ if ($@) {
+ $self->error ($@);
+ $self->{dbh}->rollback;
+ return undef;
+ }
+ return 1;
+}
+
+# Clear the kaserver sync attribute. Called by attr(). Returns true on
+# success and false on failure, setting the object error if it fails.
+sub kaserver_clear {
+ my ($self, $user, $host, $time) = @_;
+ $time ||= time;
+ my @trace = ($user, $host, $time);
+ my $name = $self->{name};
+ eval {
+ my $sql = "select ks_name from keytab_sync where ks_name = ? and
+ ks_target = 'kaserver'";
+ my $result = $self->{dbh}->selectrow_array ($sql, undef, $name);
+ unless ($result) {
+ die "kaserver synchronization not set\n";
+ }
+ $sql = 'delete from keytab_sync where ks_name = ?';
+ $self->{dbh}->do ($sql, undef, $name);
+ $self->log_set ('type_data sync', 'kaserver', undef, @trace);
+ $self->{dbh}->commit;
+ };
+ if ($@) {
+ $self->error ($@);
+ $self->{dbh}->rollback;
+ return undef;
+ }
+ return 1;
+}
+
+##############################################################################
# Keytab retrieval
##############################################################################
@@ -183,7 +376,7 @@ sub kadmin_delprinc {
sub keytab_retrieve {
my ($self, $keytab) = @_;
my $host = $Wallet::Config::KEYTAB_REMCTL_HOST;
- unless ($host and $Wallet::Config::KEYTAB_CACHE) {
+ unless ($host and $Wallet::Config::KEYTAB_REMCTL_CACHE) {
$self->error ('keytab unchanging support not configured');
return undef;
}
@@ -195,7 +388,7 @@ sub keytab_retrieve {
if ($Wallet::Config::KEYTAB_REALM) {
$keytab .= '@' . $Wallet::Config::KEYTAB_REALM;
}
- local $ENV{KRB5CCNAME} = $Wallet::Config::KEYTAB_CACHE;
+ local $ENV{KRB5CCNAME} = $Wallet::Config::KEYTAB_REMCTL_CACHE;
my $port = $Wallet::Config::KEYTAB_REMCTL_PORT;
my $principal = $Wallet::Config::KEYTAB_REMCTL_PRINCIPAL;
my @command = ('keytab', 'retrieve', $keytab);
@@ -218,6 +411,49 @@ sub keytab_retrieve {
# Core methods
##############################################################################
+# Override attr to support setting the sync attribute.
+sub attr {
+ my ($self, $attribute, $values, $user, $host, $time) = @_;
+ undef $self->{error};
+ if ($attribute ne 'sync') {
+ $self->error ("unknown attribute $attribute");
+ return;
+ }
+ if ($values) {
+ if (@$values > 1) {
+ $self->error ('only one synchronization target supported');
+ return;
+ } elsif (@$values and $values->[0] ne 'kaserver') {
+ $self->error ("unsupported synchronization target $values->[0]");
+ return;
+ }
+ $time ||= time;
+ my @trace = ($user, $host, $time);
+ if (@$values) {
+ return $self->kaserver_set ($user, $host, $time);
+ } else {
+ return $self->kaserver_clear ($user, $host, $time);
+ }
+ } else {
+ my @targets;
+ eval {
+ my $sql = 'select ks_target from keytab_sync where ks_name = ?
+ order by ks_target';
+ my $sth = $self->{dbh}->prepare ($sql);
+ $sth->execute ($self->{name});
+ my $target;
+ while (defined ($target = $sth->fetchrow_array)) {
+ push (@targets, $target);
+ }
+ };
+ if ($@) {
+ $self->error ($@);
+ return;
+ }
+ return @targets;
+ }
+}
+
# Override create to start by creating the principal in Kerberos and only
# create the entry in the database if that succeeds. Error handling isn't
# great here since we don't have a way to communicate the error back to the
@@ -262,6 +498,7 @@ sub get {
return undef;
}
my $file = $Wallet::Config::KEYTAB_TMP . "/keytab.$$";
+ unlink $file;
return undef if not $self->kadmin_ktadd ($self->{name}, $file);
local *KEYTAB;
unless (open (KEYTAB, '<', $file)) {
@@ -275,9 +512,16 @@ sub get {
if ($!) {
my $princ = $self->{name};
$self->error ("error reading keytab for principal $princ: $!");
+ unlink $file;
return undef;
}
close KEYTAB;
+ my @sync = $self->attr ('sync');
+ if (grep { $_ eq 'kaserver' } @sync) {
+ unless ($self->kaserver_sync ($self->{name}, $file)) {
+ return undef;
+ }
+ }
unlink $file;
$self->log_action ('get', $user, $host, $time);
return $data;
@@ -331,6 +575,43 @@ methods that are overridden or behave specially for this implementation.
=over 4
+=item attr(ATTRIBUTE [, VALUES, PRINCIPAL, HOSTNAME [, DATETIME]])
+
+Sets or retrieves a given object attribute. The following attributes are
+supported:
+
+=over 4
+
+=item sync
+
+Sets the external systems to which the key of a given principal is
+synchronized. The only supported value for this attribute is C<kaserver>,
+which says to synchronize the key with an AFS Kerberos v4 kaserver.
+
+If this attribute is set on a keytab, whenever get() is called for that
+keytab, the new DES key will be extracted from that keytab and set in the
+configured AFS kaserver. The Kerberos v4 principal name will be the same as
+the Kerberos v5 principal name except that the components are separated by
+C<.> instead of C</>; the second component is truncated after the first C<.>
+if the first component is one of C<host>, C<ident>, C<imap>, C<pop>, or
+C<smtp>; and the first component is C<rcmd> if the Kerberos v5 principal
+component is C<host>. The principal name must not contain more than two
+components.
+
+=back
+
+If no other arguments besides ATTRIBUTE are given, returns the values of
+that attribute, if any, as a list. On error, returns the empty list. To
+distinguish between an error and an empty return, call error() afterwards.
+It is guaranteed to return undef unless there was an error.
+
+If other arguments are given, sets the given ATTRIBUTE values to VALUES,
+which must be a reference to an array (even if only one value is being set).
+Pass a reference to an empty array to clear the attribute values.
+PRINCIPAL, HOSTNAME, and DATETIME are stored as history information.
+PRINCIPAL should be the user who is destroying the object. If DATETIME
+isn't given, the current time is used.
+
=item create(TYPE, NAME, DBH, PRINCIPAL, HOSTNAME [, DATETIME])
This is a class method and should be called on the Wallet::Object::Keytab
diff --git a/perl/Wallet/Schema.pm b/perl/Wallet/Schema.pm
index d856433..352f497 100644
--- a/perl/Wallet/Schema.pm
+++ b/perl/Wallet/Schema.pm
@@ -322,7 +322,25 @@ oh_by stores the authenticated identity that made the change, oh_from stores
the host from which they made the change, and oh_on stores the time the
change was made.
-=head2 Storage Backend Data
+=head2 Keytab Backend Data
+
+The keytab backend supports synchronizing keys with an external system. The
+permitted external systems are listed in a normalization table:
+
+ create table sync_targets
+ (st_name varchar(255) primary key);
+ insert into sync_targets (st_name) values ('kaserver');
+
+and then the synchronization targets for a given keytab are stored in this
+table:
+
+ create table keytab_sync
+ (ks_name varchar(255)
+ not null references objects(ob_name),
+ ks_target varchar(255)
+ not null references sync_targets(st_name),
+ primary key (ks_name, ks_target));
+ create index ks_name on keytab_sync (ks_name);
The keytab backend supports restricting the allowable enctypes for a given
keytab. The permitted enctypes are listed in a normalization table:
@@ -333,12 +351,12 @@ keytab. The permitted enctypes are listed in a normalization table:
and then the restrictions for a given keytab are stored in this table:
create table keytab_enctypes
- (ke_principal varchar(255)
+ (ke_name varchar(255)
not null references objects(ob_name),
ke_enctype varchar(255)
not null references enctypes(en_name),
- primary key (ke_principal, ke_enctype));
- create index ke_principal on keytab_enctypes (ke_principal);
+ primary key (ke_name, ke_enctype));
+ create index ke_name on keytab_enctypes (ke_name);
To use this functionality, you will need to populate the enctypes table with
the enctypes that a keytab may be restricted to. Currently, there is no
diff --git a/perl/t/data/README b/perl/t/data/README
index 33ec32f..968ec6c 100644
--- a/perl/t/data/README
+++ b/perl/t/data/README
@@ -27,3 +27,20 @@ and <realm> is the Kerberos realm.
Again, I do not recommend using a production realm; the test doesn't need
a production realm and it's more secure to stick to a test realm.
+
+In order to test the AFS kaserver synchronization, you will need to grant
+the test processes access to a principal with ADMIN rights in a test AFS
+kaserver. This should not be pointed at a production cell! Create the
+following files:
+
+ test.admin Fully-qualified principal of ADMIN user
+ test.cell AFS kaserver test cell
+
+The ADMIN user will be parsed to determine the default realm for
+principals created in the kaserver. You cannot use cross-realm
+authentication for this test. This AFS kaserver Kerberos v4 realm will
+also need to be configured in your local krb.conf (but not krb.realms).
+
+The test process will create the principals wallet.one and wallet.two and
+on success will clean up after itself. If the test fails, they may be
+left behind in the AFS kaserver.
diff --git a/perl/t/keytab.t b/perl/t/keytab.t
index 7b9a067..d90699c 100755
--- a/perl/t/keytab.t
+++ b/perl/t/keytab.t
@@ -3,7 +3,7 @@
#
# t/keytab.t -- Tests for the keytab object implementation.
-use Test::More tests => 66;
+use Test::More tests => 96;
use Wallet::Config;
use Wallet::Object::Keytab;
@@ -12,7 +12,7 @@ use Wallet::Server;
# Use a local SQLite database for testing.
$Wallet::Config::DB_DRIVER = 'SQLite';
$Wallet::Config::DB_INFO = 'wallet-db';
-unlink 'wallet-db';
+unlink ('wallet-db', 'krb5cc_temp', 'krb5cc_test', 'test-acl', 'test-pid');
# Some global defaults to use.
my $user = 'admin@EXAMPLE.COM';
@@ -96,6 +96,21 @@ sub created {
return (system_quiet ('kvno', $principal) == 0);
}
+# Check whether a principal exists in the kaserver. Requires that the admin
+# and srvtab variables be set up already.
+sub created_kaserver {
+ my ($principal) = @_;
+ my $admin = $Wallet::Config::KEYTAB_AFS_ADMIN;
+ my $srvtab = $Wallet::Config::KEYTAB_AFS_SRVTAB;
+ my $realm = $Wallet::Config::KEYTAB_AFS_REALM;
+ my ($name, $instance) = split (/\./, $principal);
+ $ENV{KRBTKFILE} = 'krb4cc_temp';
+ system ("k4start -f $srvtab -r $realm -S $name -I $instance $admin"
+ . " 2>&1 >/dev/null </dev/null");
+ unlink 'krb4cc_temp';
+ return ($? == 0) ? 1 : 0;
+}
+
# Given keytab data and the principal, write it to a file and try
# authenticating using kinit.
sub valid {
@@ -111,6 +126,24 @@ sub valid {
return $result;
}
+# Given a Wallet::Object::Keytab object, the keytab data, the Kerberos v5
+# principal, and the Kerberos v4 principal, write the keytab to a file,
+# generate a srvtab, and try authenticating using k4start.
+sub valid_srvtab {
+ my ($object, $keytab, $k5, $k4) = @_;
+ open (KEYTAB, '>', 'keytab') or die "cannot create keytab: $!\n";
+ print KEYTAB $keytab;
+ close KEYTAB;
+ unless ($object->kaserver_srvtab ('keytab', $k5, 'srvtab', $k4)) {
+ warn "cannot write srvtab: ", $object->error, "\n";
+ return 0;
+ }
+ $ENV{KRBTKFILE} = 'krb4cc_temp';
+ system ("k4start -f srvtab $k4 2>&1 >/dev/null </dev/null");
+ unlink 'keytab', 'srvtab', 'krb4cc_temp';
+ return ($? == 0) ? 1 : 0;
+}
+
# Start remctld with the appropriate options to run our fake keytab backend.
sub spawn_remctld {
my ($path, $principal, $keytab) = @_;
@@ -351,7 +384,7 @@ SKIP: {
is ($one->get (@trace), undef, 'Get without configuration fails');
is ($one->error, 'keytab unchanging support not configured',
' with the right error');
- $Wallet::Config::KEYTAB_CACHE = 'krb5cc_test';
+ $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');
@@ -377,5 +410,87 @@ SKIP: {
stop_remctld;
}
+# Tests for kaserver synchronization support.
+SKIP: {
+ skip 'no keytab configuration', 30 unless -f 't/data/test.keytab';
+ skip 'no AFS kaserver configuration', 30 unless -f 't/data/test.srvtab';
+
+ # Set up our configuration.
+ $Wallet::Config::KEYTAB_FILE = 't/data/test.keytab';
+ $Wallet::Config::KEYTAB_PRINCIPAL = contents ('t/data/test.principal');
+ $Wallet::Config::KEYTAB_REALM = contents ('t/data/test.realm');
+ $Wallet::Config::KEYTAB_TMP = '.';
+ $Wallet::Config::KEYTAB_AFS_KASETKEY = '../kasetkey/kasetkey';
+ my $realm = $Wallet::Config::KEYTAB_REALM;
+ my $k5 = "wallet/one\@$realm";
+
+ # Create an object for testing and set the sync attribute.
+ my $one = eval {
+ Wallet::Object::Keytab->create ('keytab', 'wallet/one', $dbh, @trace)
+ };
+ ok (defined ($one), 'Creating wallet/one succeeds');
+ is ($one->attr ('foo', [ 'bar' ], @trace), undef,
+ 'Setting unknown attribute fails');
+ is ($one->error, 'unknown attribute foo', ' with the right error');
+ my @targets = $one->attr ('foo');
+ is (scalar (@targets), 0, ' and getting an unknown attribute fails');
+ is ($one->error, 'unknown attribute foo', ' with the right error');
+ is ($one->attr ('sync', [ 'foo' ], @trace), undef,
+ ' and setting an unknown sync target fails');
+ is ($one->error, 'unsupported synchronization target foo',
+ ' with the right error');
+ is ($one->attr ('sync', [ 'kaserver', 'bar' ], @trace), undef,
+ ' and setting two targets fails');
+ is ($one->error, 'only one synchronization target supported',
+ ' with the right error');
+ is ($one->attr ('sync', [ 'kaserver' ], @trace), 1,
+ ' but setting only kaserver works');
+ @targets = $one->attr ('sync');
+ is (scalar (@targets), 1, ' and now one target is set');
+ is ($targets[0], 'kaserver', ' and it is correct');
+ is ($one->error, undef, ' and there is no error');
+
+ # Finally, we can test.
+ is ($one->get (@trace), undef, 'Get without configuration fails');
+ is ($one->error, 'kaserver synchronization not configured',
+ ' with the right error');
+ $Wallet::Config::KEYTAB_AFS_ADMIN = contents ('t/data/test.admin');
+ my $k4_realm = $Wallet::Config::KEYTAB_AFS_ADMIN;
+ $k4_realm =~ s/^[^\@]+\@//;
+ $Wallet::Config::KEYTAB_AFS_REALM = $k4_realm;
+ my $k4 = "wallet.one\@$k4_realm";
+ is ($one->get (@trace), undef, ' and still fails with just admin');
+ is ($one->error, 'kaserver synchronization not configured',
+ ' with the right error');
+ $Wallet::Config::KEYTAB_AFS_SRVTAB = 't/data/test.srvtab';
+ my $keytab = $one->get (@trace);
+ if (defined ($keytab)) {
+ ok (1, ' and now get works');
+ } else {
+ is ($one->error, '', ' and now get works');
+ }
+ ok (valid_srvtab ($one, $keytab, $k5, $k4), ' and the srvtab is valid');
+ ok (! -f "./srvtab.$$", ' and the temporary file was cleaned up');
+
+ # Now remove the sync attribute and make sure things aren't synced.
+ is ($one->attr ('sync', [], @trace), 1, 'Clearing sync works');
+ @targets = $one->attr ('sync');
+ is (scalar (@targets), 0, ' and now there is no attribute');
+ is ($one->error, undef, ' and no error');
+ $keytab = $one->get (@trace);
+ ok (defined ($keytab), ' and get still works');
+ ok (! valid_srvtab ($one, $keytab, $k5, $k4), ' but the srvtab does not');
+ ok (created_kaserver ('wallet.one'), ' and the principal is still there');
+
+ # Put it back and make sure it works again.
+ is ($one->attr ('sync', [ 'kaserver' ], @trace), 1, 'Setting sync works');
+ $keytab = $one->get (@trace);
+ ok (defined ($keytab), ' and get works');
+ ok (valid_srvtab ($one, $keytab, $k5, $k4), ' and the srvtab is valid');
+
+ # Destroy the principal.
+ is ($one->destroy (@trace), 1, 'Destroying wallet/one works');
+}
+
# Clean up.
unlink ('wallet-db', 'krb5cc_temp', 'krb5cc_test', 'test-acl', 'test-pid');
diff --git a/perl/t/schema.t b/perl/t/schema.t
index 6829700..02993ae 100755
--- a/perl/t/schema.t
+++ b/perl/t/schema.t
@@ -13,7 +13,7 @@ ok (defined $schema, 'Wallet::Schema creation');
ok ($schema->isa ('Wallet::Schema'), ' and class verification');
my @sql = $schema->sql;
ok (@sql > 0, 'sql() returns something');
-is (scalar (@sql), 22, ' and returns the right number of statements');
+is (scalar (@sql), 26, ' and returns the right number of statements');
# Create a SQLite database to use for create.
unlink 'wallet-db';