From 2b05e1d33eff84aec21202d09821a54c95446a24 Mon Sep 17 00:00:00 2001 From: Bill MacAllister Date: Sun, 3 Apr 2016 18:40:00 +0000 Subject: Add ad-keytab, update Wallet::Config * This ad-keytab is useful in the initial setup of AD as a keytab store for wallet. * Change configuration variables to correctly reflect that some values are relative distinguished names. * Add a configuration variable for the base distinguished name for ActiveDirectory. --- contrib/ad-keytab | 610 +++++++++++++++++++++++++++++++++++++++++++ perl/lib/Wallet/Config.pm | 78 ++++-- perl/lib/Wallet/Kadmin/AD.pm | 51 ++-- 3 files changed, 691 insertions(+), 48 deletions(-) create mode 100755 contrib/ad-keytab diff --git a/contrib/ad-keytab b/contrib/ad-keytab new file mode 100755 index 0000000..2af9f85 --- /dev/null +++ b/contrib/ad-keytab @@ -0,0 +1,610 @@ +#!/usr/bin/perl -w +# +# Create, update, delete, and display keytabs stored in Active Directory. +# +# Written by Bill MacAllister +# Copyright 2016 Dropbox, Inc. +# +# See LICENSE for licensing terms. + +############################################################################## +# Declarations +############################################################################## + +use Authen::SASL; +use Carp; +use Getopt::Long; +use IPC::Run qw( run timeout ); +use Net::LDAP; +use Pod::Usage; +use strict; + +my $opt_ad_server; +my $opt_base_dn; +my $opt_computer_rdn; +my $opt_config; +my $opt_debug; +my $opt_dump; +my $opt_help; +my $opt_manual; +my $opt_realm; +my $opt_user_rdn; + +# Configuration variables +our $AD_DEBUG; +our $AD_SERVER; +our $AD_COMPUTER_RDN; +our $AD_USER_RDN; +our $KEYTAB_REALM; +our $AD_BASE_DN; + +############################################################################## +# Subroutines +############################################################################## + +# Write messages to standard output and check the return status +sub msg { + my @msgs = @_; + for my $m (@msgs) { + print STDOUT $m . "\n" or croak("Problem printing to STDOUT"); + } + return; +} + +# Write debugging messages +sub dbg { + my ($m) = @_; + msg("DEBUG:$m"); + return; +} + +# Decode Active Directory's userAccountControl attribute +# Flags are powers of two starting at zero. +sub list_userAccountControl { + my ($uac) = @_; + my @flags = ( + 'SCRIPT', + 'ACCOUNTDISABLE', + 'HOMEDIR_REQUIRED', + 'LOCKOUT', + 'PASSWD_NOTREQD', + 'PASSWD_CANT_CHANGE', + 'ENCRYPTED_TEXT_PWD_ALLOWED', + 'TEMP_DUPLICATE_ACCOUNT', + 'NORMAL_ACCOUNT', + 'INTERDOMAIN_TRUST_ACCOUNT', + 'WORKSTATION_TRUST_ACCOUNT', + 'SERVER_TRUST_ACCOUNT', + 'DONT_EXPIRE_PASSWORD', + 'MNS_LOGON_ACCOUNT', + 'SMARTCARD_REQUIRED', + 'TRUSTED_FOR_DELEGATION', + 'NOT_DELEGATED', + 'USE_DES_KEY_ONLY', + 'DONT_REQ_PREAUTH', + 'PASSWORD_EXPIRED', + 'TRUSTED_TO_AUTH_FOR_DELEGATION', + 'PARTIAL_SECRETS_ACCOUNT' + ); + + my $flag_list; + my $comma = ''; + for (my $i=0; $inew(mechanism => 'GSSAPI'); + $ldap = Net::LDAP->new($AD_SERVER, onerror => 'die'); + my $mesg = eval { $ldap->bind(undef, sasl => $sasl) }; + }; + if ($@) { + my $error = $@; + die "ldap bind to AD failed: $error\n"; + } + return $ldap; +} + +# Take a principal and split into parts. The parts are keytab type, +# keytab identifier, the base dn, an LDAP filter, and if the keytab +# type is host the host name. +sub kerberos_attrs { + my ($principal) = @_; + + my %attr = (); + my $dn; + my $host; + my $k_type; + my $k_id; + if ($principal =~ m,^(host|service)/(\S+),xms) { + $attr{type} = $1; + $attr{id} = $2; + if ($attr{type} eq 'host') { + $attr{base} = $AD_COMPUTER_RDN . ',' . $AD_BASE_DN; + $attr{host} = $attr{id}; + $attr{host} =~ s/[.].*//; + $attr{dn} = "cn=$attr{host},$attr{base}"; + $attr{filter} = "(samAccountName=$attr{host}\$)"; + } elsif ($attr{'type'} eq 'service') { + $attr{base} = $AD_USER_RDN . ',' . $AD_BASE_DN; + $attr{dn} = "cn=srv-$attr{id},$attr{base}"; + $attr{filter} = "(servicePrincipalName=$attr{type}/$attr{id})"; + } + } + if ($AD_DEBUG) { + for my $a (sort keys %attr) { + dbg("$a = $attr{$a}"); + } + } + return %attr; +} + +# Perform an LDAP search against AD and return information about +# service and host accounts. +sub ad_show { + my ($principal, $kattr_ref) = @_; + + my $ldap = ldap_connect(); + my %kattr = %{$kattr_ref}; + my $base = $kattr{base}; + my $filter = $kattr{filter}; + my @attrs = (); + if (!$opt_dump) { + @attrs = ( + 'distinguishedName', 'objectclass', + 'dnsHostname', 'msds-KeyVersionNumber', + 'msds-SupportedEncryptionTypes', 'name', + 'servicePrincipalName', 'samAccountName', + 'userAccountControl', 'userPrincipalName', + 'whenChanged', 'whenCreated', + ); + } + + if ($AD_DEBUG) { + dbg("base:$base filter:$filter scope:subtree\n"); + } + + my $result; + eval { + $result = $ldap->search( + base => $base, + scope => 'subtree', + filter => $filter, + attrs => \@attrs + ); + }; + if ($@) { + my $error = $@; + die "LDAP search error: $error\n"; + } + if ($result->code) { + msg("INFO base:$base filter:$filter scope:subtree\n"); + die $result->error; + } + if ($AD_DEBUG) { + dbg('returned: ' . $result->count); + } + if ($result->count > 0) { + for my $entry ($result->entries) { + for my $attr ( sort $entry->attributes ) { + my $out = ''; + if ($attr =~ /userAccountControl/xmsi) { + my $val = $entry->get_value($attr); + $out = "$attr: $val"; + $out .= ' (' . list_userAccountControl($val) . ')'; + msg($out); + } else { + my $val_ref = $entry->get_value($attr, asref => 1); + my @vals = @{$val_ref}; + for my $val (@vals) { + msg("$attr: $val"); + } + } + } + } + } else { + msg("$kattr{type}/$kattr{id} not found"); + } + msg(' '); + return; +} + +# Check to see if a keytab exists +sub ad_exists { + my ($principal, $kattr_ref) = @_; + + my $ldap = ldap_connect(); + my %kattr = %{$kattr_ref}; + my $base = $kattr{base}; + my $filter = $kattr{filter}; + my @attrs = ('objectClass', 'msds-KeyVersionNumber'); + if ($AD_DEBUG) { + dbg("base:$base filter:$filter scope:subtree\n"); + } + + my $result; + eval { + $result = $ldap->search( + base => $base, + scope => 'subtree', + filter => $filter, + attrs => \@attrs + ); + }; + if ($@) { + my $error = $@; + die "LDAP search error: $error\n"; + } + if ($result->code) { + msg("INFO base:$base filter:$filter scope:subtree\n"); + die $result->error; + } + if ($AD_DEBUG) { + dbg('returned: ' . $result->count); + } + if ($result->count > 1) { + msg('ERROR: too many AD entries for this keytab'); + for my $entry ($result->entries) { + msg('INFO: dn found ' . $entry->dn . "\n"); + } + die("INFO: use show to examine the problem\n"); + } + if ($result->count) { + for my $entry ($result->entries) { + return $entry->get_value('msds-KeyVersionNumber'); + } + } else { + return 0; + } + return; +} + +# Run a shell command. In this case the command will always be msktutil. +sub run_cmd { + my @cmd = @_; + + if ($AD_DEBUG) { + dbg('running command:' . join(q{ }, @cmd)); + } + + my $in; + my $out; + my $err; + my $err_flag; + eval { + run(\@cmd, \$in, \$out, \$err, timeout(60)); + if ($?) { + my $this_err = $?; + $err_flag = 1; + if ($this_err) { + msg('ERROR:' . $?); + } + if ($err) { + msg('ERROR (err):' . $err); + } + } + }; + if ($@) { + msg('ERROR (status):' . $@); + $err_flag = 1; + } + if ($err_flag) { + msg('ERROR: Problem executing:' . join(q{ }, @cmd)); + die "FATAL: Execution failed\n"; + } + + msg($out); + return; +} + +# Either create or update a keytab for the principal. Return the name +# of the keytab file created. +sub ad_create_update { + my ($principal, $file, $action) = @_; + my @cmd = ('/usr/sbin/msktutil'); + push @cmd, '--' . $action; + push @cmd, '--server', $AD_SERVER; + push @cmd, '--enctypes', '0x4'; + push @cmd, '--enctypes', '0x8'; + push @cmd, '--enctypes', '0x10'; + push @cmd, '--keytab', $file; + if ($KEYTAB_REALM) { + push @cmd, '--realm', $KEYTAB_REALM; + } + if ($principal =~ m,^host/(\S+),xms) { + my $fqdn = $1; + my $host = $fqdn; + $host =~ s/[.].*//xms; + push @cmd, '--base', $AD_COMPUTER_RDN; + push @cmd, '--dont-expire-password'; + push @cmd, '--computer-name', $host; + push @cmd, '--upn', "host/$fqdn"; + push @cmd, '--hostname', $fqdn; + } elsif ($principal =~ m,^service/(\S+),xms) { + my $service_id = $1; + push @cmd, '--base', $AD_USER_RDN; + push @cmd, '--use-service-account'; + push @cmd, '--service', "service/$service_id"; + push @cmd, '--account-name', "srv-${service_id}"; + push @cmd, '--no-pac'; + } + run_cmd(@cmd); + return; +} + +# Delete a principal from Kerberos. For AD this means just delete the +# object using LDAP. +sub ad_delete { + my ($principal, $kattr_ref) = @_; + + my %kattr = %{$kattr_ref}; + if (!ad_exists($principal, $kattr_ref)) { + msg("WARN: the keytab for $principal does not appear to exist."); + msg("INFO: attempting the delete anyway.\n"); + } + + my $ldap = ldap_connect(); + my $msgid = $ldap->delete($kattr{dn}); + if ($msgid->code) { + my $m; + $m .= "ERROR: Problem deleting $kattr{dn}\n"; + $m .= $msgid->error; + die $m; + } + return 1; +} + +############################################################################## +# Main Routine +############################################################################## + +# Get options +GetOptions( + 'ad_server=s' => \$opt_ad_server, + 'base_dn=s' => \$opt_base_dn, + 'computer_rdn=s' => \$opt_computer_rdn, + 'config=s' => \$opt_config, + 'debug' => \$opt_debug, + 'dump' => \$opt_dump, + 'help' => \$opt_help, + 'manual' => \$opt_manual, + 'realm' => \$opt_realm, + 'user_rdn=s' => \$opt_user_rdn +); + +# Help the user +if ($opt_manual) { + pod2usage(-verbose => 2); +} +if ($opt_help || !$ARGV[0]) { + pod2usage(-verbose => 0); +} + +# Make sure that we have kerberos credentials and that KRB5CCNAME +# points to them. +if (!$ENV{'KRB5CCNAME'}) { + msg('ERROR: Kerberos credentials are required ... try kinit'); + pod2usage(-verbose => 0); +} + +# Read the configuration file or croak +my $conf_file; +if ($opt_config) { + if (-e $opt_config) { + $conf_file = $opt_config; + } else { + msg("ERROR: Config file ($opt_config) not found"); + pod2usage(-verbose => 0); + } +} elsif ($ENV{'ADKEYTAB'}) { + $conf_file = $ENV{'ADKEYTAB'}; +} elsif (-e '.ad-keytab.conf') { + $conf_file = '.ad-keytab.conf'; +} else { + $conf_file = '/etc/wallet/wallet.conf'; +} +do $conf_file or die (($@ || $!) . "\n"); + +# Process command line options +if ($opt_ad_server) { + $AD_SERVER = $opt_ad_server; +} +if ($opt_base_dn) { + $AD_BASE_DN = $opt_base_dn; +} +if ($opt_computer_rdn) { + $AD_COMPUTER_RDN = $opt_computer_rdn; +} +if ($opt_user_rdn) { + $AD_USER_RDN = $opt_user_rdn; +} +if ($opt_debug) { + $AD_DEBUG = 1; +} + +# -- Get command line arguments +my $action = shift; +my $id = shift; +my $keytab; +if ($ARGV[0]) { + $keytab = shift; +} else { + $keytab = '/etc/krb5.keytab'; +} + +my %kattr = kerberos_attrs($id); +# Validate that the keytab id makes sense for the keytab type +if ($kattr{type} eq 'service') { + if ($kattr{id} =~ /[.]/xms) { + msg('ERROR: service principal names may not contain periods'); + pod2usage(-verbose => 0); + } + if (length($kattr{id}) > 22) { + msg('ERROR: service principal name too long'); + pod2usage(-verbose => 0); + } +} elsif ($kattr{type} eq 'host') { + if ($kattr{id} !~ /[.]/xms) { + msg('ERROR: FQDN is required'); + pod2usage(-verbose => 0); + } +} else { + msg("ERROR: unknown keytab type $kattr{type}"); + pod2usage(-verbose => 0); +} + +if ($action =~ /^(create|update)/xms) { + ad_create_update($id, $keytab, $1); +} elsif ($action =~ /^del/xms) { + ad_delete($id, \%kattr); +} elsif ($action =~ /^sh/xms) { + ad_show($id, \%kattr); +} else { + msg("ERROR: unknown action $action"); + pod2usage(-verbose => 0); +} + +exit; + +__END__ + +=head1 NAME + +ad-keytab + +=head1 SYNOPSIS + +ad-keytab create|update|delete|show keytab-id [keytab-file] +[--ad_server=hostname] [--computer_rdn=dn] [--user_rdn] [--dump] +[--help] [--manual] [--debug] + +=head1 DESCRIPTION + +This script is a wrapper around msktutil and ldapsearch to simplify +the creation of host and service keytabs. The script is useful for +boot strapping the kerberos credentials required to use Active +Directory as a backend keytab store for wallet. The script shares +the wallet configuration file. + +Generally, two keytabs will need to be created to setup update. One +host keytab for the wallet server host and one service keytab for +wallet to use when connecting to an Active Directory Domain +Controller. + +Note, this script does not update the Wallet database which means +any keytabs created by it will be invisible from wallet. + +=head1 ACTIONS + +=over 4 + +=item create + +Add a keytab to AD and update the keytab file. Fails if the keytab +already exists. + +=item update + +Update an existing keytab in AD and update the keytab file. Fails if +the keytab does not exist. + +=item delete + +Delete a keytab from AD and remove it from the keytab file. + +=item show + +Show AD's view of the account corresponding to the keytab. This action +does not use msktutil and queries AD directly using LDAP. + +=back + +=head1 OPTIONS AND ARGUMENTS + +=over 4 + +=item keytab-id + +This is either host principal name of the form host/ or a +service principal name of the form service/. Service keytab +identifiers cannot be longer than 18 characters because of an +ActiveDirectory restriction. + +=item keytab-filename + +The name of the keytab file. Defaults to /etc/krb5.keytab. + +=item --conf=filename + +The configuration file to read. The script searches for a configuration +file in the following order. + + * The command line switch --conf + * The environment variable ADKEYTAB + * The file .ad-keytab.conf + * The file /etc/ad-keytab.conf + +=item --ad_server=hostname + +The name of the Active Directory host to connect to. It is important +what the script contact only _one_ server due to the fact that +propagation within an Active Directory domain can be quite slow. + +=item --base_dn=ou=org,dc=domain,dc=tld + +The base distinguished name holding both computer and user accounts. + +=item --computer_rdn=dn + +The relative distinguished name to use as the base DN for both the +creation of host keytabs and searches of Active Directory. The +distinguished name formed will be computer_rdn,base_dn. + +=item --user_rdn=dn + +The relative distinguished name to use as the base DN for ldap +searches of Active Directory for service keytabs. The distinguished +name formed will be user_rdn_rdn,base_dn. + +=item --dump + +When displaying keytab attributes show all of the attributes. + +=item --help + +Displays help text. + +=item --manual + +Displays more complete help text. + +=item --debug + +Turns on debugging displays. + +=back + +=head1 SEE ALSO + +Set the documentation for Wallet::Config for configuration information, i.e. +perldoc Wallet::Config. + +=head1 AUTHOR + +Bill MacAllister + +=cut diff --git a/perl/lib/Wallet/Config.pm b/perl/lib/Wallet/Config.pm index 6515756..2222aba 100644 --- a/perl/lib/Wallet/Config.pm +++ b/perl/lib/Wallet/Config.pm @@ -415,40 +415,39 @@ our $KEYTAB_TMP; =back -The following parameters are specific to generating keytabs from Active -Directory (KEYTAB_KRBTYPE is set to C). +The following parameters are specific to generating keytabs from +Active Directory (KEYTAB_KRBTYPE is set to C). =over 4 -=item AD_CACHE - -Specifies the ticket cache to use when manipulating Active Directory objects. -The ticket cache must be for a principal able to bind to Active Directory and -run B. +=item AD_BASE_DN -AD_CACHE must be set to use Active Directory support. +The base distinguished name of the ActiveDirectory instance. This is +use when Wallet uses LDAP directly to examine objects in Active +Directory. =cut -our $AD_CACHE; +our $AD_BASE_DN; -=item AD_COMPUTER_DN +=item AD_COMPUTER_RDN -The LDAP base DN for computer objects inside Active Directory. All keytabs of -the form host/ will be mapped to objects with a C of -the portion under this DN. +The LDAP base DN for computer objects inside Active Directory. All +keytabs of the form host/ will be mapped to objects with a +C of the portion under this DN. -AD_COMPUTER_DN must be set if using Active Directory as the keytab backend. +AD_COMPUTER_RDN must be set if using Active Directory as the keytab +backend. =cut -our $AD_COMPUTER_DN; +our $AD_COMPUTER_RDN; =item AD_DEBUG -If set to true, asks for some additional debugging information, such as the -B command, to be logged to syslog. These debugging messages will be -logged to the C facility. +If set to true, asks for some additional debugging information, such +as the B command, to be logged to syslog. These debugging +messages will be logged to the C facility. =cut @@ -464,17 +463,25 @@ default PATH. our $AD_MSKTUTIL = 'msktutil'; -=item AD_USER_DN +=item AD_SERVER + +The hostname of the Active Directory Domain Controller. + +=cut + +our $AD_SERVER; + +=item AD_USER_RDN The LDAP base DN for user objects inside Active Directory. All keytabs of the form service/ will be mapped to objects with a C matching the wallet object name under this DN. -AD_USER_DN must be set if using Active Directory as the keytab backend. +AD_USER_RDN must be set if using Active Directory as the keytab backend. =cut -our $AD_USER_DN; +our $AD_USER_RDN; =back @@ -482,8 +489,9 @@ our $AD_USER_DN; 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. +keys. Neither MIT Kerberos or ActiveDirectory support retrieving an +existing keytab; downloading a keytab over the kadmin protocol or +using msktutil always rekeys the principal. For MIT Kerberos, the keytab object backend therefore optionally supports retrieving existing keys, and hence keytabs, for Kerberos principals by @@ -491,6 +499,11 @@ contacting the KDC via remctl and talking to B. This is enabled by setting the C flag on keytab objects. To configure that support, set the following variables. +For ActiveDirectory Kerberos, the keytab object backend supports +storing the keytabs on the wallet server. This functionality is +enabled by setting the configuration variable AD_KEYTAB_BUCKET. (This +had not been implemented yet.) + This is not required for Heimdal; for Heimdal, setting the C flag is all that's needed. @@ -542,6 +555,25 @@ will be used. our $KEYTAB_REMCTL_PORT; +=item AD_CACHE + +The ticket cache that hold credentials used to access the +ActiveDirectory KDC. This must be created and maintained externally. + +=cut + +our $AD_CACHE; + +=item AD_KEYTAB_BUCKET + +The path to store a copy of keytabs created. This is required for the +support of unchanging keytabs with an ActiveDirectory KDC. (This has +not been implemented yet.) + +=cut + +our $AD_KEYTAB_BUCKET = '/var/lib/wallet/keytabs'; + =back =head1 WEBAUTH KEYRING OBJECT CONFIGURATION diff --git a/perl/lib/Wallet/Kadmin/AD.pm b/perl/lib/Wallet/Kadmin/AD.pm index ec60af9..1c13ab6 100644 --- a/perl/lib/Wallet/Kadmin/AD.pm +++ b/perl/lib/Wallet/Kadmin/AD.pm @@ -1,8 +1,8 @@ # Wallet::Kadmin::AD -- Wallet Kerberos administration API for AD # -# Written by Bill MacAllister +# Written by Bill MacAllister # Copyright 2016 Russ Allbery -# Copyright 2015 Dropbox, Inc. +# Copyright 2015,2016 Dropbox, Inc. # Copyright 2007, 2008, 2009, 2010, 2014 # The Board of Trustees of the Leland Stanford Junior University # @@ -100,17 +100,19 @@ sub ldap_base_filter { my $fqdn = $1; my $host = $fqdn; $host =~ s/[.].*//xms; - $base = $Wallet::Config::AD_COMPUTER_DN; $filter = "(samAccountName=${host}\$)"; + $base = $Wallet::Config::AD_COMPUTER_RDN . ',' + . $Wallet::Config::AD_BASE_DN; } elsif ($principal =~ m,^service/(\S+),xms) { my $id = $1; - $base = $Wallet::Config::AD_USER_DN; $filter = "(servicePrincipalName=service/${id})"; + $base + = $Wallet::Config::AD_USER_RDN . ',' . $Wallet::Config::AD_BASE_DN; } return ($base, $filter); } -# TODO: Get a keytab from the keytab cache. +# TODO: Get a keytab from the keytab bucket. sub get_ad_keytab { my ($self, $principal) = @_; return; @@ -125,13 +127,16 @@ sub get_ad_keytab { sub msktutil { my ($self, $args_ref) = @_; unless (defined($Wallet::Config::KEYTAB_HOST) + and defined($Wallet::Config::KEYTAB_PRINCIPAL) + and defined($Wallet::Config::KEYTAB_FILE) and defined($Wallet::Config::KEYTAB_REALM)) { die "keytab object implementation not configured\n"; } - unless (defined($Wallet::Config::AD_CACHE) - and defined($Wallet::Config::AD_COMPUTER_DN) - and defined($Wallet::Config::AD_USER_DN)) + unless (-e $Wallet::Config::AD_MSKTUTIL + and defined($Wallet::Config::AD_BASE_DN) + and defined($Wallet::Config::AD_COMPUTER_RDN) + and defined($Wallet::Config::AD_USER_RDN)) { die "Active Directory support not configured\n"; } @@ -192,14 +197,16 @@ sub ad_create_update { my $fqdn = $1; my $host = $fqdn; $host =~ s/[.].*//xms; + push @cmd, '--base', $Wallet::Config::COMPUTER_RDN; push @cmd, '--dont-expire-password'; push @cmd, '--computer-name', $host; - push @cmd, '--upn', "host/$fqdn"; - push @cmd, '--hostname', $fqdn; + push @cmd, '--upn', "host/$fqdn"; + push @cmd, '--hostname', $fqdn; } elsif ($principal =~ m,^service/(\S+),xms) { my $service_id = $1; + push @cmd, '--base', $Wallet::Config::USER_RDN; push @cmd, '--use-service-account'; - push @cmd, '--service', "service/$service_id"; + push @cmd, '--service', "service/$service_id"; push @cmd, '--account-name', "srv-${service_id}"; push @cmd, '--no-pac'; } @@ -365,9 +372,15 @@ sub ad_delete { if ($k_type eq 'host') { my $host = $k_id; $host =~ s/[.].*//; - $dn = "cn=${host}," . $Wallet::Config::AD_COMPUTER_DN; + $dn + = "cn=${host}," + . $Wallet::Config::AD_COMPUTER_RDN . ',' + . $Wallet::Config::AD_BASE_DN; } elsif ($k_type eq 'service') { - $dn = "cn=srv-${k_id}," . $Wallet::Config::AD_USER_DN; + $dn + = "cn=srv-${k_id}," + . $Wallet::Config::AD_USER_RDN . ',' + . $Wallet::Config::AD_BASE_DN; } } @@ -435,18 +448,6 @@ using a local keytab cache. To use this class, several configuration parameters must be set. See L for details. -=head1 FILES - -=over 4 - -=item KEYTAB_TMP/keytab. - -The keytab is created in this file and then read into memory. KEYTAB_TMP -is set in the wallet configuration, and is the process ID of the -current process. The file is unlinked after being read. - -=back - =head1 LIMITATIONS Currently, this implementation calls an external B program rather -- cgit v1.2.3