diff options
| author | Jon Robertson <jonrober@stanford.edu> | 2009-12-10 14:40:59 -0800 | 
|---|---|---|
| committer | Jon Robertson <jonrober@stanford.edu> | 2009-12-10 14:40:59 -0800 | 
| commit | 0e6b6e3be0d1c544871445a580de7da502fec8c0 (patch) | |
| tree | 3639e591fbfd0114a02094729929852e4e977c3a /perl/Wallet | |
| parent | 2c5bd71125d411639b4a61116957879eebae21ad (diff) | |
Added support for Heimdal KDC
Added support for Heimdal as an alternative to MIT Kerberos.  This involved
separating out the kadmin-specific code into its own set of modules, and
changing the existing Wallet::Object::Keytab code to branch based on
which module is loaded.
Diffstat (limited to 'perl/Wallet')
| -rw-r--r-- | perl/Wallet/Kadmin.pm | 110 | ||||
| -rw-r--r-- | perl/Wallet/Kadmin/Heimdal.pm | 278 | ||||
| -rw-r--r-- | perl/Wallet/Kadmin/MIT.pm | 275 | ||||
| -rw-r--r-- | perl/Wallet/Object/Keytab.pm | 192 | 
4 files changed, 696 insertions, 159 deletions
| diff --git a/perl/Wallet/Kadmin.pm b/perl/Wallet/Kadmin.pm new file mode 100644 index 0000000..b804861 --- /dev/null +++ b/perl/Wallet/Kadmin.pm @@ -0,0 +1,110 @@ +# Wallet::Kadmin -- Kadmin module wrapper for the wallet. +# +# Written by Jon Robertson <jonrober@stanford.edu> +# Copyright 2009 Board of Trustees, Leland Stanford Jr. University +# +# See LICENSE for licensing terms. + +############################################################################## +# Modules and declarations +############################################################################## + +package Wallet::Kadmin; +require 5.006; + +use strict; +use vars qw($VERSION); + +use Wallet::Config (); + +# This version should be increased on any code change to this module.  Always +# use two digits for the minor version with a leading zero if necessary so +# that it will sort properly. +$VERSION = '0.01'; + +############################################################################## +# Constructor +############################################################################## + +# Create a new kadmin object, by finding the type requested in the wallet +# config and passing off to the proper module.  Returns the object directly +# from the specific Wallet::Kadmin::* module. +sub new { +    my ($class) = @_; +    my ($kadmin); +    if ($Wallet::Config::KEYTAB_KRBTYPE eq 'MIT') { +	require Wallet::Kadmin::MIT; +        $kadmin = Wallet::Kadmin::MIT->new (); +    } elsif ($Wallet::Config::KEYTAB_KRBTYPE eq 'Heimdal') { +	require Wallet::Kadmin::Heimdal; +        $kadmin = Wallet::Kadmin::Heimdal->new (); +    } else { +        die "keytab krb server type not set to a valid value\n"; +    } + +    return $kadmin; +} + +1; +__END__ + +############################################################################## +# Documentation +############################################################################## + +=head1 NAME + +Wallet::Kadmin - Kadmin module wrapper for wallet keytabs + +=head1 SYNOPSIS + +    my $kadmin = Wallet::Kadmin->new (); +    $kadmin->addprinc ("host/shell.example.com"); +    $kadmin->ktadd ("host/shell.example.com", "aes256-cts"); +    my $exists = $kadmin->exists ("host/oldshell.example.com"); +    $kadmin->delprinc ("host/oldshell.example.com") if $exists; + +=head1 DESCRIPTION + +Wallet::Kadmin is a wrapper to modules that provide an interface for keytab +integration with the wallet.  Each module is meant to interface with a  +specific type of Kerberos implementation, such as MIT Kerberos or Heimdal +Kerberos, and provide a standndard set of API calls used to interact with +that implementation's kadmind. + +The class simply uses Wallet::Config to find which type of kadmind we have +requested to use, and then returns an object to use for interacting with +that kadmind. + +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. + +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 METHODS + +=over 4 + +=item new() + +Finds the proper Kerberos implementation and calls the new() constructor for +that implementation's module, returning the result.  If the implementation +is not recognized or set, die with an error message. + +=head1 SEE ALSO + +kadmin(8), Wallet::Config(3), Wallet::Object::Keytab(3), wallet-backend(8) + +This module is part of the wallet system.  The current version is available +from L<http://www.eyrie.org/~eagle/software/wallet/>. + +=head1 AUTHORS + +Jon Robertson <jonrober@stanford.edu> + +=cut diff --git a/perl/Wallet/Kadmin/Heimdal.pm b/perl/Wallet/Kadmin/Heimdal.pm new file mode 100644 index 0000000..06564d2 --- /dev/null +++ b/perl/Wallet/Kadmin/Heimdal.pm @@ -0,0 +1,278 @@ +# Wallet::Kadmin::Heimdal -- Heimdal Kadmin interactions for the wallet. +# +# Written by Jon Robertson <jonrober@stanford.edu> +# Copyright 2009 Board of Trustees, Leland Stanford Jr. University +# +# See LICENSE for licensing terms. + +############################################################################## +# Modules and declarations +############################################################################## + +package Wallet::Kadmin::Heimdal; +require 5.006; + +use strict; +use vars qw($VERSION); + +use Heimdal::Kadm5 qw (KRB5_KDB_DISALLOW_ALL_TIX); + +use Wallet::Config (); + +# This version should be increased on any code change to this module.  Always +# use two digits for the minor version with a leading zero if necessary so +# that it will sort properly. +$VERSION = '0.01'; + +############################################################################## +# kadmin Interaction +############################################################################## + +# Make sure that principals are well-formed and don't contain characters that +# will cause us problems when talking to kadmin.  Takes a principal and +# returns true if it's okay, false otherwise.  Note that we do not permit +# realm information here. +sub valid_principal { +    my ($self, $principal) = @_; +    return scalar ($principal =~ m,^[\w-]+(/[\w_.-]+)?\z,); +} + +# Create a Heimdal::Kadm5 client object and return it.  It should load  +# configuration from Wallet::Config. +sub kadmin_client { +    unless (defined ($Wallet::Config::KEYTAB_PRINCIPAL) +            and defined ($Wallet::Config::KEYTAB_FILE) +            and defined ($Wallet::Config::KEYTAB_REALM)) { +        die "keytab object implementation not configured\n"; +    } + +    my $server = $Wallet::Config::KEYTAB_HOST || 'localhost'; +    my $client = Heimdal::Kadm5::Client->new( +        RaiseErrors => 1, +        Server      => $server, +        Principal   => $Wallet::Config::KEYTAB_PRINCIPAL, +        Realm       => $Wallet::Config::KEYTAB_REALM, +        Keytab      => $Wallet::Config::KEYTAB_FILE, +        ); +    return $client; +} + +############################################################################## +# Public interfaces +############################################################################## + +# Check whether a given principal already exists in Kerberos.  Returns true if +# so, false otherwise.  Throws an exception if kadmin fails. +sub exists { +    my ($self, $principal) = @_; +    return unless $self->valid_principal ($principal); +    if ($Wallet::Config::KEYTAB_REALM) { +        $principal .= '@' . $Wallet::Config::KEYTAB_REALM; +    } +    my $kadmin = $self->{client}; +    my @names = $kadmin->getPrincipals ($principal); +    if (@names) { +        return 1; +    } else { +        return 0; +    } +} + +# Create a principal in Kerberos.  Since this is only called by create, it +# throws an exception on failure rather than setting the error and returning +# undef. +sub addprinc { +    my ($self, $principal) = @_; +    unless ($self->valid_principal ($principal)) { +        die "invalid principal name $principal\n"; +    } +    return 1 if $self->exists ($principal); +    if ($Wallet::Config::KEYTAB_REALM) { +        $principal .= '@' . $Wallet::Config::KEYTAB_REALM; +    } + +    # The way Heimdal::Kadm5 works, we create a principal object, create the +    # actual principal set inactive, then randomize it and activate it. +    # TODO - Paranoia makes me want to set the password to something random +    #        on creation even if it is inactive until after randomized by +    #        module. +    my $kadmin = $self->{client}; +    my $princdata = $kadmin->makePrincipal ($principal); + +    # Disable the principal before creating, until we've randomized the  +    # password. +    my $attrs = $princdata->getAttributes; +    $attrs |= KRB5_KDB_DISALLOW_ALL_TIX; +    $princdata->setAttributes ($attrs); + +    my $password = 'inactive'; +    my $retval = eval { $kadmin->createPrincipal ($princdata, $password, 0) }; +    die "error adding principal $principal: $@" if $@; +    $retval = eval { $kadmin->randKeyPrincipal ($principal) }; +    die "error adding principal $principal: $@" if $@; +    $retval = eval { $kadmin->enablePrincipal ($principal) }; +    die "error adding principal $principal: $@" if $@; + +    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. +sub ktadd { +    my ($self, $principal, $file, @enctypes) = @_; +    unless ($self->valid_principal ($principal)) { +        die ("invalid principal name: $principal"); +    } +    if ($Wallet::Config::KEYTAB_REALM) { +        $principal .= '@' . $Wallet::Config::KEYTAB_REALM; +    } + +    my $kadmin = $self->{client}; +    my $princdata = eval { $kadmin->getPrincipal ($principal) }; + +    # Remove enctypes we don't want in this keytab.  Must find all current +    # keytypes, then remove those that do not match. +    my (%wanted); +    my $alltypes = $princdata->getKeytypes (); +    foreach (@enctypes) { $wanted{$_} = 1 } +    foreach my $key (@{$alltypes}) { +	my $keytype = ${$key}[0]; +	next if exists $wanted{$keytype}; +	eval { $princdata->delKeytypes ($keytype) }; +	die "error removing keytype $keytype from the keytab: $@" if $@; +    } +    eval { $kadmin->modifyPrincipal ($princdata) }; + +    my $retval = eval { $kadmin->extractKeytab ($princdata, $file) }; +    die "error creating keytab for principal: $@" if $@; + +    return 1; +} + +# Delete a principal from Kerberos.  Return true if successful, false +# otherwise.  If the deletion fails, sets the error.  If the principal doesn't +# exist, return success; we're bringing reality in line with our expectations. +sub delprinc { +    my ($self, $principal) = @_; +    unless ($self->valid_principal ($principal)) { +        die ("invalid principal name: $principal"); +    } +    my $exists = eval { $self->exists ($principal) }; +    die $@ if $@; +    if (not $exists) { +        return 1; +    } +    if ($Wallet::Config::KEYTAB_REALM) { +        $principal .= '@' . $Wallet::Config::KEYTAB_REALM; +    } + +    my $kadmin = $self->{client}; +    my $retval = eval { $kadmin->deletePrincipal ($principal) }; +    die "error deleting $principal: $@" if $@; +    return 1; +} + +############################################################################## +# Documentation +############################################################################## + +# Create a new MIT kadmin object.  Very empty for the moment, but later it +# will probably fill out if we go to using a module rather than calling  +# kadmin directly. +sub new { +    my ($class) = @_; +    my $self = { +	client => kadmin_client (), +    }; +    bless ($self, $class); +    return $self; +} + +1; +__END__ + +############################################################################## +# Documentation +############################################################################## + +=head1 NAME + +Wallet::Kadmin::MIT - MIT admin interactions for wallet keytabs + +=head1 SYNOPSIS + +    my $kadmin = Wallet::Kadmin::MIT->new (); +    $kadmin->addprinc ("host/shell.example.com"); +    $kadmin->ktadd ("host/shell.example.com", "aes256-cts"); +    my $exists = $kadmin->exists ("host/oldshell.example.com"); +    $kadmin->delprinc ("host/oldshell.example.com") if $exists; + +=head1 DESCRIPTION + +Wallet::Kadmin::MIT is an interface for keytab integration with the wallet, +specifically for using kadmin to create, delete, and add enctypes to keytabs. +It implments the wallet kadmin API and provides the necessary glue to MIT +Kerberos installs for each of these functions, while allowing the wallet +to keep the details of what type of Kerberos installation is being used  +abstracted. + +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. + +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 METHODS + +=over 4 + +=item addprinc(PRINCIPAL) + +Adds a new principal with a given name.  The principal is created with a  +random password, and any other flags set by Wallet::Config.  Returns true on  +success, or throws an error if there was a failure in adding the principal. +If the principal already exists, return true as we are bringing our  +expectations in line with reality. + +=item addprinc(PRINCIPAL) + +Removes a principal with the given name.  Returns true on success, or throws  +an error if there was a failure in removing the principal.  If the principal +does not exist, return true as we are bringing our expectations in line with +reality. + +=item ktadd(PRINCIPAL, FILE, ENCTYPES) + +Creates a new keytab for the given principal, as the given file, limited to +the enctypes supplied.  The enctype values must be enctype strings recognized  +by Kerberos (strings like C<aes256-cts> or C<des-cbc-crc>).  An error is  +thrown on failure or if the creation fails, otherwise true is returned. + +=back + +=head1 LIMITATIONS + +Currently, this implementation calls an external B<kadmin> program rather + than using a native Perl module and therefore requires B<kadmin> be  +installed and parses its output.  It may miss some error conditions if the +output of B<kadmin> ever changes. + +=head1 SEE ALSO + +kadmin(8), Wallet::Config(3), Wallet::Object::Keytab(3), wallet-backend(8) + +This module is part of the wallet system.  The current version is available +from L<http://www.eyrie.org/~eagle/software/wallet/>. + +=head1 AUTHORS + +Russ Allbery <rra@stanford.edu> +Jon Robertson <jonrober@stanford.edu> + +=cut diff --git a/perl/Wallet/Kadmin/MIT.pm b/perl/Wallet/Kadmin/MIT.pm new file mode 100644 index 0000000..b7d4913 --- /dev/null +++ b/perl/Wallet/Kadmin/MIT.pm @@ -0,0 +1,275 @@ +# Wallet::Kadmin::MIT -- MIT Kadmin interactions for the wallet. +# +# Written by Russ Allbery <rra@stanford.edu> +# Pulled into a module by Jon Robertson <jonrober@stanford.edu> +# Copyright 2007, 2008, 2009 Board of Trustees, Leland Stanford Jr. University +# +# See LICENSE for licensing terms. + +############################################################################## +# Modules and declarations +############################################################################## + +package Wallet::Kadmin::MIT; +require 5.006; + +use strict; +use vars qw($VERSION); + +use Wallet::Config (); + +# This version should be increased on any code change to this module.  Always +# use two digits for the minor version with a leading zero if necessary so +# that it will sort properly. +$VERSION = '0.01'; + +############################################################################## +# kadmin Interaction +############################################################################## + +# Make sure that principals are well-formed and don't contain characters that +# will cause us problems when talking to kadmin.  Takes a principal and +# returns true if it's okay, false otherwise.  Note that we do not permit +# realm information here. +sub valid_principal { +    my ($self, $principal) = @_; +    return scalar ($principal =~ m,^[\w-]+(/[\w_.-]+)?\z,); +} + +# Run a kadmin command and capture the output.  Returns the output, either as +# a list of lines or, in scalar context, as one string.  The exit status of +# kadmin is often worthless. +sub kadmin { +    my ($self, $command) = @_; +    unless (defined ($Wallet::Config::KEYTAB_PRINCIPAL) +            and defined ($Wallet::Config::KEYTAB_FILE) +            and defined ($Wallet::Config::KEYTAB_REALM)) { +        die "keytab object implementation not configured\n"; +    } +    my @args = ('-p', $Wallet::Config::KEYTAB_PRINCIPAL, '-k', '-t', +                $Wallet::Config::KEYTAB_FILE, '-q', $command); +    push (@args, '-s', $Wallet::Config::KEYTAB_HOST) +        if $Wallet::Config::KEYTAB_HOST; +    push (@args, '-r', $Wallet::Config::KEYTAB_REALM) +        if $Wallet::Config::KEYTAB_REALM; +    my $pid = open (KADMIN, '-|'); +    if (not defined $pid) { +        die "cannot fork: $!\n"; +    } elsif ($pid == 0) { +        # TODO - How should I handle the db handle? +        # Don't use die here; it will get trapped as an exception.  Also be +        # careful about our database handles.  (We still lose if there's some +        # other database handle open we don't know about.) +        #$object->{dbh}->{InactiveDestroy} = 1; +        unless (open (STDERR, '>&STDOUT')) { +            warn "wallet: cannot dup stdout: $!\n"; +            exit 1; +        } +        unless (exec ($Wallet::Config::KEYTAB_KADMIN, @args)) { +            warn "wallet: cannot run $Wallet::Config::KEYTAB_KADMIN: $!\n"; +            exit 1; +        } +    } +    local $_; +    my @output; +    while (<KADMIN>) { +        if (/^wallet: cannot /) { +            s/^wallet: //; +            die $_; +        } +        push (@output, $_) unless /Authenticating as principal/; +    } +    close KADMIN; +    return wantarray ? @output : join ('', @output); +} + +############################################################################## +# Public interfaces +############################################################################## + +# Check whether a given principal already exists in Kerberos.  Returns true if +# so, false otherwise.  Throws an exception if kadmin fails. +sub exists { +    my ($self, $principal) = @_; +    return unless $self->valid_principal ($principal); +    if ($Wallet::Config::KEYTAB_REALM) { +        $principal .= '@' . $Wallet::Config::KEYTAB_REALM; +    } +    my $output = $self->kadmin ("getprinc $principal"); +    if ($output =~ /^get_principal: /) { +        return; +    } else { +        return 1; +    } +} + +# Create a principal in Kerberos.  Since this is only called by create, it +# throws an exception on failure rather than setting the error and returning +# undef. +sub addprinc { +    my ($self, $principal) = @_; +    unless ($self->valid_principal ($principal)) { +        die "invalid principal name $principal\n"; +    } +    return 1 if $self->exists ($principal); +    if ($Wallet::Config::KEYTAB_REALM) { +        $principal .= '@' . $Wallet::Config::KEYTAB_REALM; +    } +    my $flags = $Wallet::Config::KEYTAB_FLAGS || ''; +    my $output = $self->kadmin ("addprinc -randkey $flags $principal"); +    if ($output =~ /^add_principal: (.*)/m) { +        die "error adding principal $principal: $1\n"; +    } +    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. +sub ktadd { +    my ($self, $principal, $file, @enctypes) = @_; +    unless ($self->valid_principal ($principal)) { +        die ("invalid principal name: $principal"); +    } +    if ($Wallet::Config::KEYTAB_REALM) { +        $principal .= '@' . $Wallet::Config::KEYTAB_REALM; +    } +    my $command = "ktadd -q -k $file"; +    if (@enctypes) { +        @enctypes = map { /:/ ? $_ : "$_:normal" } @enctypes; +        $command .= ' -e "' . join (' ', @enctypes) . '"'; +    } +    my $output = eval { $self->kadmin ("$command $principal") }; +    die ($@) if ($@); +    if ($output =~ /^(?:kadmin|ktadd): (.*)/m) { +        die ("error creating keytab for $principal: $1"); +    } +    return 1; +} + +# Delete a principal from Kerberos.  Return true if successful, false +# otherwise.  If the deletion fails, sets the error.  If the principal doesn't +# exist, return success; we're bringing reality in line with our expectations. +sub delprinc { +    my ($self, $principal) = @_; +    unless ($self->valid_principal ($principal)) { +        die ("invalid principal name: $principal"); +    } +    my $exists = eval { $self->exists ($principal) }; +    die $@ if $@; +    if (not $exists) { +        return 1; +    } +    if ($Wallet::Config::KEYTAB_REALM) { +        $principal .= '@' . $Wallet::Config::KEYTAB_REALM; +    } +    my $output = eval { $self->kadmin ("delprinc -force $principal") }; +    die $@ if $@; +    if ($output =~ /^delete_principal: (.*)/m) { +        die ("error deleting $principal: $1"); +    } +    return 1; +} + +############################################################################## +# Documentation +############################################################################## + +# Create a new MIT kadmin object.  Very empty for the moment, but later it +# will probably fill out if we go to using a module rather than calling  +# kadmin directly. +sub new { +    my ($class) = @_; +    my $self = { +    }; +    bless ($self, $class); +    return $self; +} + +1; +__END__ + +############################################################################## +# Documentation +############################################################################## + +=head1 NAME + +Wallet::Kadmin::MIT - MIT admin interactions for wallet keytabs + +=head1 SYNOPSIS + +    my $kadmin = Wallet::Kadmin::MIT->new (); +    $kadmin->addprinc ("host/shell.example.com"); +    $kadmin->ktadd ("host/shell.example.com", "aes256-cts"); +    my $exists = $kadmin->exists ("host/oldshell.example.com"); +    $kadmin->delprinc ("host/oldshell.example.com") if $exists; + +=head1 DESCRIPTION + +Wallet::Kadmin::MIT is an interface for keytab integration with the wallet, +specifically for using kadmin to create, delete, and add enctypes to keytabs. +It implments the wallet kadmin API and provides the necessary glue to MIT +Kerberos installs for each of these functions, while allowing the wallet +to keep the details of what type of Kerberos installation is being used  +abstracted. + +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. + +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 METHODS + +=over 4 + +=item addprinc(PRINCIPAL) + +Adds a new principal with a given name.  The principal is created with a  +random password, and any other flags set by Wallet::Config.  Returns true on  +success, or throws an error if there was a failure in adding the principal. +If the principal already exists, return true as we are bringing our  +expectations in line with reality. + +=item addprinc(PRINCIPAL) + +Removes a principal with the given name.  Returns true on success, or throws  +an error if there was a failure in removing the principal.  If the principal +does not exist, return true as we are bringing our expectations in line with +reality. + +=item ktadd(PRINCIPAL, FILE, ENCTYPES) + +Creates a new keytab for the given principal, as the given file, limited to +the enctypes supplied.  The enctype values must be enctype strings recognized  +by Kerberos (strings like C<aes256-cts> or C<des-cbc-crc>).  An error is  +thrown on failure or if the creation fails, otherwise true is returned. + +=back + +=head1 LIMITATIONS + +Currently, this implementation calls an external B<kadmin> program rather + than using a native Perl module and therefore requires B<kadmin> be  +installed and parses its output.  It may miss some error conditions if the +output of B<kadmin> ever changes. + +=head1 SEE ALSO + +kadmin(8), Wallet::Config(3), Wallet::Object::Keytab(3), wallet-backend(8) + +This module is part of the wallet system.  The current version is available +from L<http://www.eyrie.org/~eagle/software/wallet/>. + +=head1 AUTHORS + +Russ Allbery <rra@stanford.edu> +Jon Robertson <jonrober@stanford.edu> + +=cut diff --git a/perl/Wallet/Object/Keytab.pm b/perl/Wallet/Object/Keytab.pm index 4cb8dff..1732070 100644 --- a/perl/Wallet/Object/Keytab.pm +++ b/perl/Wallet/Object/Keytab.pm @@ -17,6 +17,7 @@ use vars qw(@ISA $VERSION);  use Wallet::Config ();  use Wallet::Object::Base; +use Wallet::Kadmin;  @ISA = qw(Wallet::Object::Base); @@ -26,160 +27,6 @@ use Wallet::Object::Base;  $VERSION = '0.06';  ############################################################################## -# kadmin Interaction -############################################################################## - -# Make sure that principals are well-formed and don't contain characters that -# will cause us problems when talking to kadmin.  Takes a principal and -# returns true if it's okay, false otherwise.  Note that we do not permit -# realm information here. -sub valid_principal { -    my ($self, $principal) = @_; -    return scalar ($principal =~ m,^[\w-]+(/[\w_.-]+)?\z,); -} - -# Run a kadmin command and capture the output.  Returns the output, either as -# a list of lines or, in scalar context, as one string.  The exit status of -# kadmin is often worthless. -sub kadmin { -    my ($self, $command) = @_; -    unless (defined ($Wallet::Config::KEYTAB_PRINCIPAL) -            and defined ($Wallet::Config::KEYTAB_FILE) -            and defined ($Wallet::Config::KEYTAB_REALM)) { -        die "keytab object implementation not configured\n"; -    } -    my @args = ('-p', $Wallet::Config::KEYTAB_PRINCIPAL, '-k', '-t', -                $Wallet::Config::KEYTAB_FILE, '-q', $command); -    push (@args, '-s', $Wallet::Config::KEYTAB_HOST) -        if $Wallet::Config::KEYTAB_HOST; -    push (@args, '-r', $Wallet::Config::KEYTAB_REALM) -        if $Wallet::Config::KEYTAB_REALM; -    my $pid = open (KADMIN, '-|'); -    if (not defined $pid) { -        die "cannot fork: $!\n"; -    } elsif ($pid == 0) { -        # Don't use die here; it will get trapped as an exception.  Also be -        # careful about our database handles.  (We still lose if there's some -        # other database handle open we don't know about.) -        $self->{dbh}->{InactiveDestroy} = 1; -        unless (open (STDERR, '>&STDOUT')) { -            warn "wallet: cannot dup stdout: $!\n"; -            exit 1; -        } -        unless (exec ($Wallet::Config::KEYTAB_KADMIN, @args)) { -            warn "wallet: cannot run $Wallet::Config::KEYTAB_KADMIN: $!\n"; -            exit 1; -        } -    } -    local $_; -    my @output; -    while (<KADMIN>) { -        if (/^wallet: cannot /) { -            s/^wallet: //; -            die $_; -        } -        push (@output, $_) unless /Authenticating as principal/; -    } -    close KADMIN; -    return wantarray ? @output : join ('', @output); -} - -# Check whether a given principal already exists in Kerberos.  Returns true if -# so, false otherwise.  Throws an exception if kadmin fails. -sub kadmin_exists { -    my ($self, $principal) = @_; -    return unless $self->valid_principal ($principal); -    if ($Wallet::Config::KEYTAB_REALM) { -        $principal .= '@' . $Wallet::Config::KEYTAB_REALM; -    } -    my $output = $self->kadmin ("getprinc $principal"); -    if ($output =~ /^get_principal: /) { -        return; -    } else { -        return 1; -    } -} - -# Create a principal in Kerberos.  Since this is only called by create, it -# throws an exception on failure rather than setting the error and returning -# undef. -sub kadmin_addprinc { -    my ($self, $principal) = @_; -    unless ($self->valid_principal ($principal)) { -        die "invalid principal name $principal\n"; -    } -    return 1 if $self->kadmin_exists ($principal); -    if ($Wallet::Config::KEYTAB_REALM) { -        $principal .= '@' . $Wallet::Config::KEYTAB_REALM; -    } -    my $flags = $Wallet::Config::KEYTAB_FLAGS || ''; -    my $output = $self->kadmin ("addprinc -randkey $flags $principal"); -    if ($output =~ /^add_principal: (.*)/m) { -        die "error adding principal $principal: $1\n"; -    } -    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. -sub kadmin_ktadd { -    my ($self, $principal, $file, @enctypes) = @_; -    unless ($self->valid_principal ($principal)) { -        $self->error ("invalid principal name: $principal"); -        return; -    } -    if ($Wallet::Config::KEYTAB_REALM) { -        $principal .= '@' . $Wallet::Config::KEYTAB_REALM; -    } -    my $command = "ktadd -q -k $file"; -    if (@enctypes) { -        @enctypes = map { /:/ ? $_ : "$_:normal" } @enctypes; -        $command .= ' -e "' . join (' ', @enctypes) . '"'; -    } -    my $output = eval { $self->kadmin ("$command $principal") }; -    if ($@) { -        $self->error ($@); -        return; -    } elsif ($output =~ /^(?:kadmin|ktadd): (.*)/m) { -        $self->error ("error creating keytab for $principal: $1"); -        return; -    } -    return 1; -} - -# Delete a principal from Kerberos.  Return true if successful, false -# otherwise.  If the deletion fails, sets the error.  If the principal doesn't -# exist, return success; we're bringing reality in line with our expectations. -sub kadmin_delprinc { -    my ($self, $principal) = @_; -    unless ($self->valid_principal ($principal)) { -        $self->error ("invalid principal name: $principal"); -        return; -    } -    my $exists = eval { $self->kadmin_exists ($principal) }; -    if ($@) { -        $self->error ($@); -        return; -    } elsif (not $exists) { -        return 1; -    } -    if ($Wallet::Config::KEYTAB_REALM) { -        $principal .= '@' . $Wallet::Config::KEYTAB_REALM; -    } -    my $output = eval { $self->kadmin ("delprinc -force $principal") }; -    if ($@) { -        $self->error ($@); -        return; -    } elsif ($output =~ /^delete_principal: (.*)/m) { -        $self->error ("error deleting $principal: $1"); -        return; -    } -    return 1; -} - -##############################################################################  # AFS kaserver synchronization  ############################################################################## @@ -607,16 +454,41 @@ sub attr_show {      return $output;  } +# Override new to start by creating a handle for the kadmin module we're +# using. +sub new { +    my ($class, $type, $name, $dbh) = @_; +     my $self = { +        dbh    => $dbh, +        kadmin => undef, +    }; +    bless $self, $class; +    my $kadmin = Wallet::Kadmin->new (); +    $self->{kadmin} = $kadmin; + +    $self = $class->SUPER::new ($type, $name, $dbh); +    $self->{kadmin} = $kadmin; +    return $self; +} +  # 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  # caller.  sub create {      my ($class, $type, $name, $dbh, $creator, $host, $time) = @_; -    my $self = { dbh => $dbh }; +    my $self = {  +	dbh    => $dbh, +	kadmin => undef, +    };      bless $self, $class; -    $self->kadmin_addprinc ($name); -    return $class->SUPER::create ($type, $name, $dbh, $creator, $host, $time); +    my $kadmin = Wallet::Kadmin->new (); +    $self->{kadmin} = $kadmin; +    $kadmin->addprinc ($name); +     +    $self = $class->SUPER::create ($type, $name, $dbh, $creator, $host, $time); +    $self->{kadmin} = $kadmin; +    return $self;  }  # Override destroy to delete the principal out of Kerberos as well. @@ -645,7 +517,8 @@ sub destroy {          $self->{dbh}->rollback;          return;      } -    return if not $self->kadmin_delprinc ($self->{name}); +    my $kadmin = $self->{kadmin}; +    return if not $kadmin->delprinc ($self->{name});      return $self->SUPER::destroy ($user, $host, $time);  } @@ -673,7 +546,8 @@ sub get {      my $file = $Wallet::Config::KEYTAB_TMP . "/keytab.$$";      unlink $file;      my @enctypes = $self->attr ('enctypes'); -    return if not $self->kadmin_ktadd ($self->{name}, $file, @enctypes); +    my $kadmin = $self->{kadmin}; +    return if not $kadmin->ktadd ($self->{name}, $file, @enctypes);      local *KEYTAB;      unless (open (KEYTAB, '<', $file)) {          my $princ = $self->{name}; | 
