]> eyrie.org Git - kerberos/perl-kerberos.git/commitdiff
Add initial keytab and principal implementation
authorRuss Allbery <eagle@eyrie.org>
Fri, 7 Mar 2014 04:33:07 +0000 (20:33 -0800)
committerRuss Allbery <eagle@eyrie.org>
Fri, 7 Mar 2014 04:33:07 +0000 (20:33 -0800)
Flesh out the Authen::Kerberos API considerably by adding some
glue for the first useful application.  Add classes to represent
keytabs, principals, and keytab entries.  Add more portability
glue so that we can start probing the nature of the local Kerberos
library.  Add tests for correct handling of principal objects.

17 files changed:
.gitignore
Build.PL
MANIFEST.SKIP
lib/Authen/Kerberos.pm
lib/Authen/Kerberos.xs
lib/Authen/Kerberos/Kadmin.xs
lib/Authen/Kerberos/Keytab.pm [new file with mode: 0644]
lib/Authen/Kerberos/KeytabEntry.pm [new file with mode: 0644]
lib/Authen/Kerberos/Principal.pm [new file with mode: 0644]
portable/krb5.h [new file with mode: 0644]
portable/stdbool.h [new file with mode: 0644]
portable/system.h [new file with mode: 0644]
t/kerberos/basic.t
typemap
util/context.c [new file with mode: 0644]
util/croak.c
util/util.h

index 4bbe5917b9eec1dc01ba4ab715e362f2a96061b7..da78e78cbbc09563209540cd5253cd314dcf51c1 100644 (file)
@@ -4,7 +4,9 @@
 /MYMETA.yml
 /_build/
 /blib/
+/config.log
 /cover_db
+/glue/
 /lib/Authen/Kerberos.c
 /lib/Authen/Kerberos/Kadmin.c
 *.o
index 26c71de76247645811c3e848db239f7cf93a7d3a..7aec24797ea8c9379412b304f31c8fce37e6dbae 100644 (file)
--- a/Build.PL
+++ b/Build.PL
@@ -29,9 +29,161 @@ use autodie;
 use strict;
 use warnings;
 
+use Config::AutoConf;
+use File::Basename qw(basename);
+use File::Path qw(remove_tree);
+use File::Spec;
 use IPC::System::Simple qw(capturex);
 use Module::Build;
 
+# Check whether it's possible to link a program that uses a particular
+# function.  This is written like a Config::AutoConf method and should ideally
+# be incorporated into that module.  This macro caches its result in the
+# ac_cv_func_FUNCTION variable.
+#
+# $self         - The Config::AutoConf state object
+# $function     - The function to check for
+# $found_ref    - Code reference to call if the function was found
+# $notfound_ref - Code reference to call if the function wasn't found
+#
+# Returns: True if the function was found, false otherwise
+sub check_func {
+    my ($self, $function, $found_ref, $notfound_ref) = @_;
+    $self = $self->_get_instance();
+
+    # Build the name of the cache variable.
+    my $cache_name = $self->_cache_name('func', $function);
+
+    # Wrap the actual check in a closure so that we can use check_cached.
+    my $check_sub = sub {
+        my $have_func = $self->link_if_else($self->lang_call(q{}, $function));
+        if ($have_func) {
+            if (defined($found_ref) && ref($found_ref) eq 'CODE') {
+                $found_ref->();
+            }
+        } else {
+            if (defined($notfound_ref) && ref($notfound_ref) eq 'CODE') {
+                $notfound_ref->();
+            }
+        }
+        return $have_func;
+    };
+
+    # Run the check and cache the results.
+    return $self->check_cached($cache_name, "for $function", $check_sub);
+}
+
+# The same as check_func, but takes a list of functions to look for and checks
+# for each in turn.  Define HAVE_FUNCTION for each function that was found,
+# and also run the $found_ref code each time a function was found.  Run the
+# $notfound_ref code each time a function wasn't found.  Both code references
+# are passed the name of the function that was found.
+#
+# $self          - The Config::AutoConf state object
+# $functions_ref - Reference to an array of functions to check for
+# $found_ref     - Code reference to call if a function was found
+# $notfound_ref  - Code reference to call if a function wasn't found
+#
+# Returns: True if all functions were found, false otherwise.
+sub check_funcs {
+    my ($self, $functions_ref, $user_found_ref, $user_notfound_ref) = @_;
+    $self = $self->_get_instance();
+
+    # Build the code reference to run when a function was found.  This defines
+    # a HAVE_FUNCTION symbol, plus runs the current $found_ref if there is
+    # one.
+    my $func_found_ref = sub {
+        my ($function) = @_;
+
+        # Generate the name of the symbol we'll define.
+        my $have_func_name = 'HAVE_' . uc($function);
+        $have_func_name =~ tr/_A-Za-z0-9/_/c;
+
+        # Define the symbol.
+        $self->define_var($have_func_name, 1,
+            "Defined when $function is available");
+
+        # Run the user-provided hook, if there is one.
+        if (defined($user_found_ref) && ref($user_found_ref) eq 'CODE') {
+            $user_found_ref->($function);
+        }
+    };
+
+    # Go through the list of functions and call check_func for each one.  We
+    # generate new closures for the found and not-found functions that pass in
+    # the relevant function name.
+    my $return = 1;
+    for my $function (@{$functions_ref}) {
+        my $found_ref    = sub { $func_found_ref->($function) };
+        my $notfound_ref = sub { $user_notfound_ref->($function) };
+        $return &= check_func($self, $function, $found_ref, $notfound_ref);
+    }
+    return $return;
+}
+
+# Returns C code that includes the given headers.  Used to construct prologues
+# for check functions.
+#
+# @headers - The headers to include
+#
+# Returns: C source as a string that includes those headers
+sub include {
+    my @headers = @_;
+    my $result  = q{};
+    for my $header (@headers) {
+        $result .= "#include <$header>\n";
+    }
+    return $result;
+}
+
+# Probes the C compilation environment for the information required to build
+# the Kerberos compatibility layer.  This should be a Perl equivalent of the
+# m4/krb5.m4 Autoconf macros from rra-c-util, plus the additional probes
+# needed for the compatibility layer for building the code.  Writes the
+# results to glue/config.h and returns a list of extra C files to add to the
+# module build.
+#
+# $build - The module build object, used to add additional libraries
+#
+# Returns: List of extra directories to add to the module build
+#  Throws: Text exception if the module cannot be built in this environment
+sub config_kerberos {
+    my ($build) = @_;
+    my $config = Config::AutoConf->new;
+
+    # Checks needed for the generic portability layer.
+    $config->check_default_headers;
+    if (!$config->check_header('stdbool.h')) {
+        $config->check_type('_Bool');
+    }
+    $config->check_type('ssize_t', undef, undef, include('sys/types.h'));
+
+    # If the user passed extra flags into Build.PL, use them for probes.
+    if ($build->extra_linker_flags) {
+        my $flags = $build->extra_linker_flags;
+        my @flags = ref($flags) ? @{$flags} : ($flags);
+        $config->push_link_flags(@flags);
+    }
+
+    # Determine which Kerberos header to use.
+    my $header = $config->check_headers('krb5.h', 'krb5/krb5.h');
+    if (!defined($header)) {
+        die "No Kerberos headers found (tried krb5.h and krb5/krb5.h)\n";
+    }
+
+    # Check for functions that are different between Heimdal and MIT.
+    check_funcs($config, ['krb5_xfree']);
+    my $includes = include($header) . $config->_default_includes;
+    if ($config->check_decl('krb5_kt_free_entry', undef, undef, $includes)) {
+        $config->define_var('HAVE_DECL_KRB5_KT_FREE_ENTRY',
+            1, 'Defined when krb5_kt_free_entry is declared');
+    }
+
+    # Write out the configuration.
+    $config->write_config_h('glue/config.h');
+    return;
+}
+
 # Get the compiler and linker flags for Heimdal.  Currently, a new enough
 # Heimdal to support pkg-config is required.
 my $compiler_flags = capturex('pkg-config', '--cflags', 'heimdal-kadm-server');
@@ -45,16 +197,20 @@ my $build = Module::Build->new(
     dist_version         => '0.02',
     license              => 'mit',
     recursive_test_files => 1,
+    add_to_cleanup       => [qw(config.log cover_db glue/*.o util/*.o)],
 
     # XS configuration.
-    c_source             => 'util',
-    extra_compiler_flags => [split(q{ }, $compiler_flags)],
+    #<<<
+    c_source             => ['glue', 'util'],
+    extra_compiler_flags => ['-I.', split(q{ }, $compiler_flags)],
     extra_linker_flags   => [split(q{ }, $linker_flags)],
+    #>>>
 
     # Other package relationships.
     configure_requires => {
+        'Config::AutoConf'    => 0,
         'IPC::System::Simple' => 0,
-        'Module::Build'       => '0.28',
+        'Module::Build'       => '0.3604',
         autodie               => 0,
         perl                  => '5.010',
     },
@@ -64,5 +220,31 @@ my $build = Module::Build->new(
     },
 );
 
+# Create the directory that will be used for config.h and stub files.
+remove_tree('glue');
+mkdir('glue');
+
+# Write out the config.h file and get the list of files to add to the build.
+my @c_files = config_kerberos($build);
+
+# We can't just add the C source files directly to the build for a couple of
+# reasons.  First, Perl ships its own config.h, so we need to be sure we
+# include our own instead of Perl's before building any source, since all of
+# the files (copied from rra-c-util, so we don't want to change them) include
+# config.h as the first action.  Second, Module::Build can only handle
+# directories of supplemental files, not individual file names.
+#
+# We deal with both of these issues by creating stub files in a subdirectory
+# named glue that include glue/config.h and then the actual C source file.
+for my $file (@c_files) {
+    my $glue_file = File::Spec->catfile('glue', basename($file));
+    open(my $wrapper, '>', $glue_file);
+    say {$wrapper} '#include <glue/config.h>'
+      or die "Cannot write to $glue_file: $!\n";
+    say {$wrapper} "#include <$file>"
+      or die "Cannot write to $glue_file: $!\n";
+    close($wrapper);
+}
+
 # Generate the build script.
 $build->create_build_script;
index 20f90302bf2fcbd1f891d2a99b0748f1f047fd87..d30d0f45d3a37db1c974c7c87a88d2bcb4d142c5 100644 (file)
@@ -5,6 +5,9 @@
 
 # Avoid generated build files.
 \bblib/
+^config\.log$
+^glue/
+^lib/Authen/Kerberos\.c$
 ^lib/Authen/Kerberos/Kadmin\.c$
 \.o$
 
index 7aa01340354563459e6631cecb12aaa0aaf07d36..bddf9e66556b667f24e91444abf8056ed954c58d 100644 (file)
@@ -38,7 +38,6 @@ use warnings;
 
 use base qw(DynaLoader);
 
-use Authen::Kerberos::Exception;
 use Exporter qw(import);
 
 our $VERSION;
@@ -48,6 +47,14 @@ BEGIN {
     $VERSION = '0.02';
 }
 
+# The C code also creates some other types of objects and throws
+# Authen::Kerberos::Exception objects, and callers expect to be able to call
+# methods on those objects.  Load all of the Perl classes for the caller that
+# provide additional object methods so that the caller doesn't have to
+# remember to do so.
+use Authen::Kerberos::Exception;
+use Authen::Kerberos::Principal;
+
 # Load the binary module.
 bootstrap Authen::Kerberos $VERSION;
 
@@ -83,7 +90,8 @@ later.
 
 =head1 CLASS METHODS
 
-All class methods throw Authen::Kerberos::Exception objects on any error.
+All class methods throw Authen::Kerberos::Exception objects on any
+Kerberos error.
 
 =over 4
 
@@ -94,6 +102,30 @@ state.  All further operations must be done with this object.
 
 =back
 
+=head1 INSTANCE METHODS
+
+All class methods throw Authen::Kerberos::Exception objects on any
+Kerberos error.
+
+=over 4
+
+=item keytab(KEYTAB)
+
+Open the given keytab and return a new Authen::Kerberos::Keytab object for
+it.  KEYTAB should be in the form I<type>:I<residual> where I<type> is one
+of the keytab type identifiers recognized by the underlying Kerberos
+library.  The most common type is C<FILE>, in which case I<residual> is a
+path.
+
+=item principal(NAME)
+
+Convert the given principal name to an Authen::Kerberos::Principal object.
+Normally there is no need to use this method since all Authen::Kerberos
+APIs that take principal names will accept the string form of the principal
+name and convert it internally.
+
+=back
+
 =head1 AUTHOR
 
 Russ Allbery <eagle@eyrie.org>
index 51029346cddac9087c0bfc09fd3398552f5217df..60540e5d1dea4f363cc77000e8e842541039334c 100644 (file)
  * DEALINGS IN THE SOFTWARE.
  */
 
+#include <glue/config.h>
+#include <portable/krb5.h>
+#include <portable/system.h>
+
 #include <EXTERN.h>
 #include <perl.h>
 #include <XSUB.h>
 
-#include <krb5.h>
+#include <util/util.h>
 
 /*
  * This typedefs are needed for xsubpp to work its magic with type translation
- * to Perl objects.  The krb5_context pointer is used as the Authen::Kerberos
- * object, wrapped in an SV and blessed.
+ * to Perl objects.  This strategy can only be used for objects that don't
+ * need to stash a copy of the Kerberos context, such as the Kerberos context
+ * object itself or ones where all methods on the object do not need a
+ * context.
  */
 typedef krb5_context Authen__Kerberos;
 
+/*
+ * Wrapper structs for additional data structures returned by the Kerberos
+ * libraries.  We wrap the API data structure so that we can store a reference
+ * to the Authen::Kerberos object (the Kerberos context), ensuring that the
+ * Kerberos context is not freed before the secondary object and that the same
+ * Kerberos context is used for all operations on that object.
+ */
+typedef struct {
+    SV *ctx;
+    krb5_keytab keytab;
+} *Authen__Kerberos__Keytab;
+
+typedef struct {
+    SV *ctx;
+    krb5_keytab_entry entry;
+} *Authen__Kerberos__KeytabEntry;
+
+typedef struct {
+    SV *ctx;
+    krb5_principal principal;
+} *Authen__Kerberos__Principal;
+
 
 /* XS code below this point. */
 
@@ -75,3 +103,240 @@ DESTROY(self)
     if (self != NULL)
         krb5_free_context(self);
 }
+
+
+Authen::Kerberos::Keytab
+keytab(self, name)
+    Authen::Kerberos self
+    const char *name
+  PREINIT:
+    Authen__Kerberos__Keytab kt;
+    krb5_error_code code;
+    krb5_keytab keytab;
+  CODE:
+{
+    CROAK_NULL_SELF(self, "Authen::Kerberos", "keytab_open");
+    code = krb5_kt_resolve(self, name, &keytab);
+    if (code != 0)
+        krb5_croak(self, code, "krb5_kt_resolve", FALSE);
+    kt = malloc(sizeof(*kt));
+    if (kt == NULL)
+        croak("cannot allocate memory");
+    kt->keytab = keytab;
+    kt->ctx = SvRV(ST(0));
+    SvREFCNT_inc_simple_void_NN(kt->ctx);
+    RETVAL = kt;
+}
+  OUTPUT:
+    RETVAL
+
+
+Authen::Kerberos::Principal
+principal(self, name)
+    Authen::Kerberos self
+    const char *name
+  PREINIT:
+    krb5_error_code code;
+    krb5_principal principal;
+    Authen__Kerberos__Principal princ;
+  CODE:
+{
+    CROAK_NULL_SELF(self, "Authen::Kerberos", "principal");
+    code = krb5_parse_name(self, name, &principal);
+    if (code != 0)
+        krb5_croak(self, code, "krb5_parse_name", FALSE);
+    princ = malloc(sizeof(*princ));
+    if (princ == NULL)
+        croak("cannot allocate memory");
+    princ->principal = principal;
+    princ->ctx = SvRV(ST(0));
+    SvREFCNT_inc_simple_void_NN(princ->ctx);
+    RETVAL = princ;
+}
+  OUTPUT:
+    RETVAL
+
+
+MODULE = Authen::Kerberos       PACKAGE = Authen::Kerberos::Keytab
+
+void
+DESTROY(self)
+    Authen::Kerberos::Keytab self
+  PREINIT:
+    krb5_context ctx;
+  CODE:
+{
+    if (self == NULL)
+        return;
+    ctx = krb5_context_from_sv(self->ctx, "Authen::Kerberos::Keytab");
+    krb5_kt_close(ctx, self->keytab);
+    SvREFCNT_dec(self->ctx);
+    free(self);
+}
+
+
+void
+entries(self, callback)
+    Authen::Kerberos::Keytab self
+    SV *callback
+  PREINIT:
+    krb5_context ctx;
+    krb5_kt_cursor cursor;
+    krb5_keytab_entry entry;
+    Authen__Kerberos__KeytabEntry obj;
+    krb5_error_code code;
+    size_t count = 0;
+  PPCODE:
+{
+    CROAK_NULL_SELF(self, "Authen::Kerberos::Keytab", "entries");
+    ctx = krb5_context_from_sv(self->ctx, "Authen::Kerberos::Keytab");
+
+    /* Start the cursor. */
+    code = krb5_kt_start_seq_get(ctx, self->keytab, &cursor);
+    if (code != 0)
+        krb5_croak(ctx, code, "krb5_kt_start_seq_get", FALSE);
+
+    /* For each entry, either count it or add it to the output stack. */
+    code = krb5_kt_next_entry(ctx, self->keytab, &entry, &cursor);
+    while (code != KRB5_KT_END) {
+        count++;
+        if (GIMME_V == G_ARRAY) {
+            SV *obj;
+            Authen__Kerberos__KeytabEntry wrapper;
+
+            wrapper = malloc(sizeof(*wrapper));
+            if (wrapper == NULL)
+                croak("cannot allocate memory");
+            wrapper->ctx = self->ctx;
+            SvREFCNT_inc_simple_void_NN(wrapper->ctx);
+            wrapper->entry = entry;
+            obj = sv_newmortal();
+            sv_setref_pv(obj, "Authen::Kerberos::KeytabEntry", wrapper);
+            XPUSHs(obj);
+        }
+        code = krb5_kt_next_entry(ctx, self->keytab, &entry, &cursor);
+    }
+
+    /* Make sure everything was successful and close the cursor. */
+    if (code != KRB5_KT_END)
+        krb5_croak(ctx, code, "krb5_kt_next_entry", FALSE);
+    krb5_kt_end_seq_get(ctx, self->keytab, &cursor);
+
+    /* If we're in a scalar context, push the count. */
+    if (GIMME_V != G_ARRAY) {
+        ST(0) = newSViv(count);
+        sv_2mortal(ST(0));
+        XSRETURN(1);
+    }
+}
+
+
+MODULE = Authen::Kerberos       PACKAGE = Authen::Kerberos::KeytabEntry
+
+void
+DESTROY(self)
+    Authen::Kerberos::KeytabEntry self
+  PREINIT:
+    krb5_context ctx;
+  CODE:
+{
+    if (self == NULL)
+        return;
+    ctx = krb5_context_from_sv(self->ctx, "Authen::Kerberos::KeytabEntry");
+    krb5_kt_free_entry(ctx, &self->entry);
+    SvREFCNT_dec(self->ctx);
+    free(self);
+}
+
+
+krb5_kvno
+kvno(self)
+    Authen::Kerberos::KeytabEntry self
+  CODE:
+{
+    CROAK_NULL_SELF(self, "Authen::Kerberos::KeytabEntry", "kvno");
+    RETVAL = self->entry.vno;
+}
+  OUTPUT:
+    RETVAL
+
+
+Authen::Kerberos::Principal
+principal(self)
+    Authen::Kerberos::KeytabEntry self
+  PREINIT:
+    krb5_context ctx;
+    krb5_principal princ;
+    krb5_error_code code;
+    Authen__Kerberos__Principal principal;
+  CODE:
+{
+    CROAK_NULL_SELF(self, "Authen::Kerberos::KeytabEntry", "principal");
+    ctx = krb5_context_from_sv(self->ctx, "Authen::Kerberos::KeytabEntry");
+    if (principal == NULL)
+        croak("cannot allocate memory");
+    code = krb5_copy_principal(ctx, self->entry.principal, &princ);
+    if (code != 0)
+        krb5_croak(ctx, code, "krb5_copy_principal", FALSE);
+    principal = malloc(sizeof(*principal));
+    principal->ctx = self->ctx;
+    SvREFCNT_inc_simple_void_NN(principal->ctx);
+    principal->principal = princ;
+    RETVAL = principal;
+}
+  OUTPUT:
+    RETVAL
+
+
+krb5_timestamp
+timestamp(self)
+    Authen::Kerberos::KeytabEntry self
+  CODE:
+{
+    CROAK_NULL_SELF(self, "Authen::Kerberos::KeytabEntry", "timestamp");
+    RETVAL = self->entry.timestamp;
+}
+  OUTPUT:
+    RETVAL
+
+
+MODULE = Authen::Kerberos       PACKAGE = Authen::Kerberos::Principal
+
+void
+DESTROY(self)
+    Authen::Kerberos::Principal self
+  PREINIT:
+    krb5_context ctx;
+  CODE:
+{
+    if (self == NULL)
+        return;
+    ctx = krb5_context_from_sv(self->ctx, "Authen::Kerberos::Principal");
+    krb5_free_principal(ctx, self->principal);
+    SvREFCNT_dec(self->ctx);
+    free(self);
+}
+
+
+SV *
+to_string(self, other = NULL, swap = 0)
+    Authen::Kerberos::Principal self
+    SV *other
+    bool swap
+  OVERLOAD: \"\"
+  PREINIT:
+    krb5_context ctx;
+    krb5_error_code code;
+    char *principal;
+  CODE:
+{
+    CROAK_NULL_SELF(self, "Authen::Kerberos::Principal", "to_string");
+    ctx = krb5_context_from_sv(self->ctx, "Authen::Kerberos::Principal");
+    code = krb5_unparse_name(ctx, self->principal, &principal);
+    if (code != 0)
+        krb5_croak(ctx, code, "krb5_unparse_name", FALSE);
+    RETVAL = newSVpv(principal, 0);
+    krb5_free_unparsed_name(ctx, principal);
+}
+  OUTPUT:
+    RETVAL
index 41b802e5a79f9227023de6f6a7845766a4a881bc..8d541bfc0d18cbb16f731afed8d7b94d1fd5ad1e 100644 (file)
@@ -37,7 +37,7 @@
 #include <perl.h>
 #include <XSUB.h>
 
-#include <krb5.h>
+#include <portable/krb5.h>
 #include <kadm5/admin.h>
 #include <kadm5/kadm5_err.h>
 
diff --git a/lib/Authen/Kerberos/Keytab.pm b/lib/Authen/Kerberos/Keytab.pm
new file mode 100644 (file)
index 0000000..4f3aad5
--- /dev/null
@@ -0,0 +1,95 @@
+# Wrapper class for Kerberos keytab objects.
+#
+# This class provides the Perl representation of a krb5_keytab structure as
+# used in the Kerberos API.  Most of the implementation is in the XS code
+# underlying Authen::Kerberos.  This file provides documentation and some
+# additional code that's easier to represent in Perl.
+#
+# Written by Russ Allbery <eagle@eyrie.org>
+# Copyright 2014
+#     The Board of Trustees of the Leland Stanford Junior University
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+package Authen::Kerberos::Keytab;
+
+use 5.010;
+use strict;
+use warnings;
+
+use Authen::Kerberos;
+
+our $VERSION;
+
+# Set $VERSION in a BEGIN block for robustness.
+BEGIN {
+    $VERSION = '0.02';
+}
+
+1;
+
+__END__
+
+=for stopwords
+Allbery keytab
+
+=head1 NAME
+
+Authen::Kerberos::Keytab - Kerberos API representation of a keytab
+
+=head1 SYNOPSIS
+
+    use Authen::Kerberos;
+
+    my $krb5 = Authen::Kerberos->new;
+    my $keytab = $krb5->keytab('FILE:/etc/krb5.keytab');
+    my @entries = $keytab->entries;
+    print 'First principal: ', $entries[0]->principal, "\n";
+
+=head1 DESCRIPTION
+
+An Authen::Kerberos::Keytab object wraps the Kerberos API
+representation of an open keytab (key table).  A keytab holds zero or
+more principal keys, usually in the form of a disk file.
+
+=head1 INSTANCE METHODS
+
+As with all Authen::Kerberos methods, an Authen::Kerberos::Exception
+object will be thrown on any Kerberos error.
+
+=over 4
+
+=item entries()
+
+In a scalar context, returns the number of entries in a keytab.  In an
+array context, returns all of the entries of the keytab as
+Authen::Kerberos::KeytabEntry objects.
+
+=back
+
+=head1 AUTHOR
+
+Russ Allbery <eagle@eyrie.org>
+
+=head1 SEE ALSO
+
+L<Authen::Kerberos>, L<Authen::Kerberos::Exception>,
+L<Authen::Kerberos::KeytabEntry>
+
+=cut
diff --git a/lib/Authen/Kerberos/KeytabEntry.pm b/lib/Authen/Kerberos/KeytabEntry.pm
new file mode 100644 (file)
index 0000000..151d650
--- /dev/null
@@ -0,0 +1,103 @@
+# Wrapper class for Kerberos keytab entry objects.
+#
+# This class provides the Perl representation of a krb5_keytab_entry structure
+# as used in the Kerberos API.  Most of the implementation is in the XS code
+# underlying Authen::Kerberos.  This file provides documentation and some
+# additional code that's easier to represent in Perl.
+#
+# Written by Russ Allbery <eagle@eyrie.org>
+# Copyright 2014
+#     The Board of Trustees of the Leland Stanford Junior University
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+package Authen::Kerberos::KeytabEntry;
+
+use 5.010;
+use strict;
+use warnings;
+
+use Authen::Kerberos;
+
+our $VERSION;
+
+# Set $VERSION in a BEGIN block for robustness.
+BEGIN {
+    $VERSION = '0.02';
+}
+
+1;
+
+__END__
+
+=for stopwords
+Allbery KVNO keytab
+
+=head1 NAME
+
+Authen::Kerberos::KeytabEntry - Kerberos API representation of a keytab entry
+
+=head1 SYNOPSIS
+
+    use Authen::Kerberos;
+
+    my $krb5 = Authen::Kerberos->new;
+    my $keytab = $krb5->keytab('FILE:/etc/krb5.keytab');
+    my @entries = $keytab->entries;
+    print 'First principal: ', $entries[0]->principal, "\n";
+
+=head1 DESCRIPTION
+
+An Authen::Kerberos::KeytabEntry object wraps the Kerberos API
+representation of an entry in a keytab (key table).  A keytab holds zero
+or more entries, usually in the form of a disk file.  Each entry has a
+key for a particular principal.
+
+=head1 INSTANCE METHODS
+
+As with all Authen::Kerberos methods, an Authen::Kerberos::Exception
+object will be thrown on any Kerberos error.
+
+=over 4
+
+=item kvno()
+
+Returns the KVNO (key version number) of this keytab entry.
+
+=item principal()
+
+Returns the principal whose key is stored in this entry, in the form of an
+Authen::Kerberos::Principal object.
+
+=item timestamp()
+
+Returns the timestamp of this keytab entry as seconds since UNIX epoch.
+
+=back
+
+=head1 AUTHOR
+
+Russ Allbery <eagle@eyrie.org>
+
+=head1 SEE ALSO
+
+L<Authen::Kerberos>, L<Authen::Kerberos::Exception>,
+L<Authen::Kerberos::Keytab>
+
+=cut
diff --git a/lib/Authen/Kerberos/Principal.pm b/lib/Authen/Kerberos/Principal.pm
new file mode 100644 (file)
index 0000000..0eaffbc
--- /dev/null
@@ -0,0 +1,105 @@
+# Wrapper class for Kerberos principal objects.
+#
+# This class provides the Perl representation of a krb5_principal structure as
+# used in the Kerberos API.  Most of the implementation is in the XS code
+# underlying Authen::Kerberos.  This file provides documentation and some
+# additional code that's easier to represent in Perl.
+#
+# Written by Russ Allbery <eagle@eyrie.org>
+# Copyright 2014
+#     The Board of Trustees of the Leland Stanford Junior University
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+package Authen::Kerberos::Principal;
+
+use 5.010;
+use strict;
+use warnings;
+
+use Authen::Kerberos;
+
+our $VERSION;
+
+# Set $VERSION in a BEGIN block for robustness.
+BEGIN {
+    $VERSION = '0.02';
+}
+
+1;
+
+__END__
+
+=for stopwords
+Allbery
+
+=head1 NAME
+
+Authen::Kerberos::Principal - Kerberos API representation of a principal name
+
+=head1 SYNOPSIS
+
+    use Authen::Kerberos;
+    use Authen::Kerberos::Principal;
+
+    my $krb5 = Authen::Kerberos->new;
+    my $principal = $krb5->principal('test@EXAMPLE.COM');
+    print "Principal: $principal\n";
+
+=head1 DESCRIPTION
+
+An Authen::Kerberos::Principal object wraps the Kerberos API
+representation of a parsed principal name.
+
+Normally, a user of the Authen::Kerberos module does not have to care
+about the existence of this object.  APIs that take principals will take
+either strings or Authen::Kerberos::Principal objects, and an
+Authen::Kerberos::Principal object will be automatically converted to a
+string when necessary.  Under the hood, this object holds a parsed form of
+the principal that unambiguously separates the principal components and
+realm without needing to use the escaping that the string display form
+uses.
+
+=head1 INSTANCE METHODS
+
+As with all Authen::Kerberos methods, an Authen::Kerberos::Exception
+object will be thrown on any Kerberos error.
+
+=over 4
+
+=item to_string()
+
+Returns the string form (the display form) of the principal.  This method
+will be automatically called if an Authen::Kerberos::Principal object is
+interpolated into a string.  It may also be called directly to retrieve
+the string form of the principal name.  Special characters in any
+principal component, such as C<@> or C</>, will be escaped using the
+normal Kerberos principal string encoding.
+
+=back
+
+=head1 AUTHOR
+
+Russ Allbery <eagle@eyrie.org>
+
+=head1 SEE ALSO
+
+L<Authen::Kerberos>, L<Authen::Kerberos::Exception>
+
+=cut
diff --git a/portable/krb5.h b/portable/krb5.h
new file mode 100644 (file)
index 0000000..4122631
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Portability wrapper around krb5.h.
+ *
+ * This header includes krb5.h and then adjusts for various portability
+ * issues, primarily between MIT Kerberos and Heimdal, so that code can be
+ * written to a consistent API.
+ *
+ * Unfortunately, due to the nature of the differences between MIT Kerberos
+ * and Heimdal, it's not possible to write code to either one of the APIs and
+ * adjust for the other one.  In general, this header tries to make available
+ * the Heimdal API and fix it for MIT Kerberos, but there are places where MIT
+ * Kerberos requires a more specific call.  For those cases, it provides the
+ * most specific interface.
+ *
+ * For example, MIT Kerberos has krb5_free_unparsed_name() whereas Heimdal
+ * prefers the generic krb5_xfree().  In this case, this header provides
+ * krb5_free_unparsed_name() for both APIs since it's the most specific call.
+ *
+ * The canonical version of this file is maintained in the rra-c-util package,
+ * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ *
+ * The authors hereby relinquish any claim to any copyright that they may have
+ * in this work, whether granted under contract or by operation of law or
+ * international treaty, and hereby commit to the public, at large, that they
+ * shall not, at any time in the future, seek to enforce any copyright in this
+ * work against any person or entity, or prevent any person or entity from
+ * copying, publishing, distributing or creating derivative works of this
+ * work.
+ */
+
+#ifndef PORTABLE_KRB5_H
+#define PORTABLE_KRB5_H 1
+
+/*
+ * Allow inclusion of config.h to be skipped, since sometimes we have to use a
+ * stripped-down version of config.h with a different name.
+ */
+#ifndef CONFIG_H_INCLUDED
+# include <config.h>
+#endif
+#include <portable/macros.h>
+
+#ifdef HAVE_KRB5_H
+# include <krb5.h>
+#else
+# include <krb5/krb5.h>
+#endif
+
+BEGIN_DECLS
+
+/* Default to a hidden visibility for all portability functions. */
+#pragma GCC visibility push(hidden)
+
+/* Heimdal: krb5_xfree, MIT: krb5_free_unparsed_name. */
+#ifdef HAVE_KRB5_XFREE
+# define krb5_free_unparsed_name(c, p) krb5_xfree(p)
+#endif
+
+/*
+ * Heimdal: krb5_kt_free_entry, MIT: krb5_free_keytab_entry_contents.  We
+ * check for the declaration rather than the function since the function is
+ * present in older MIT Kerberos libraries but not prototyped.
+ */
+#if !HAVE_DECL_KRB5_KT_FREE_ENTRY
+# define krb5_kt_free_entry(c, e) krb5_free_keytab_entry_contents((c), (e))
+#endif
+
+/* Undo default visibility change. */
+#pragma GCC visibility pop
+
+END_DECLS
+
+#endif /* !PORTABLE_KRB5_H */
diff --git a/portable/stdbool.h b/portable/stdbool.h
new file mode 100644 (file)
index 0000000..14d011b
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Portability wrapper around <stdbool.h>.
+ *
+ * Provides the bool and _Bool types and the true and false constants,
+ * following the C99 specification, on hosts that don't have stdbool.h.  This
+ * logic is based heavily on the example in the Autoconf manual.
+ *
+ * The canonical version of this file is maintained in the rra-c-util package,
+ * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ *
+ * The authors hereby relinquish any claim to any copyright that they may have
+ * in this work, whether granted under contract or by operation of law or
+ * international treaty, and hereby commit to the public, at large, that they
+ * shall not, at any time in the future, seek to enforce any copyright in this
+ * work against any person or entity, or prevent any person or entity from
+ * copying, publishing, distributing or creating derivative works of this
+ * work.
+ */
+
+#ifndef PORTABLE_STDBOOL_H
+#define PORTABLE_STDBOOL_H 1
+
+/*
+ * Allow inclusion of config.h to be skipped, since sometimes we have to use a
+ * stripped-down version of config.h with a different name.
+ */
+#ifndef CONFIG_H_INCLUDED
+# include <config.h>
+#endif
+
+#if HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# if HAVE__BOOL
+#  define bool _Bool
+# else
+#  ifdef __cplusplus
+typedef bool _Bool;
+#  elif _WIN32
+#   include <windef.h>
+#   define bool BOOL
+#  else
+typedef unsigned char _Bool;
+#   define bool _Bool
+#  endif
+# endif
+# define false 0
+# define true  1
+# define __bool_true_false_are_defined 1
+#endif
+
+/*
+ * If we define bool and don't tell Perl, it will try to define its own and
+ * fail.  Only of interest for programs that also include Perl headers.
+ */
+#ifndef HAS_BOOL
+# define HAS_BOOL 1
+#endif
+
+#endif /* !PORTABLE_STDBOOL_H */
diff --git a/portable/system.h b/portable/system.h
new file mode 100644 (file)
index 0000000..e2cf047
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * Standard system includes and portability adjustments.
+ *
+ * Declarations of routines and variables in the C library.  Including this
+ * file is the equivalent of including all of the following headers,
+ * portably:
+ *
+ *     #include <sys/types.h>
+ *     #include <stdarg.h>
+ *     #include <stdbool.h>
+ *     #include <stddef.h>
+ *     #include <stdio.h>
+ *     #include <stdlib.h>
+ *     #include <stdint.h>
+ *     #include <string.h>
+ *     #include <strings.h>
+ *     #include <unistd.h>
+ *
+ * Missing functions are provided via #define or prototyped if available from
+ * the portable helper library.  Also provides some standard #defines.
+ *
+ * The canonical version of this file is maintained in the rra-c-util package,
+ * which can be found at <http://www.eyrie.org/~eagle/software/rra-c-util/>.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ *
+ * The authors hereby relinquish any claim to any copyright that they may have
+ * in this work, whether granted under contract or by operation of law or
+ * international treaty, and hereby commit to the public, at large, that they
+ * shall not, at any time in the future, seek to enforce any copyright in this
+ * work against any person or entity, or prevent any person or entity from
+ * copying, publishing, distributing or creating derivative works of this
+ * work.
+ */
+
+#ifndef PORTABLE_SYSTEM_H
+#define PORTABLE_SYSTEM_H 1
+
+/* Make sure we have our configuration information. */
+#include <config.h>
+
+/* BEGIN_DECL and __attribute__. */
+#include <portable/macros.h>
+
+/* A set of standard ANSI C headers.  We don't care about pre-ANSI systems. */
+#if HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+#include <stdarg.h>
+#include <stddef.h>
+#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#if HAVE_STRINGS_H
+# include <strings.h>
+#endif
+#include <sys/types.h>
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+/* SCO OpenServer gets int32_t from here. */
+#if HAVE_SYS_BITYPES_H
+# include <sys/bitypes.h>
+#endif
+
+/* Get the bool type. */
+#include <portable/stdbool.h>
+
+/* Windows provides snprintf under a different name. */
+#ifdef _WIN32
+# define snprintf _snprintf
+#endif
+
+/* Windows does not define ssize_t. */
+#ifndef HAVE_SSIZE_T
+typedef ptrdiff_t ssize_t;
+#endif
+
+/*
+ * POSIX requires that these be defined in <unistd.h>.  If one of them has
+ * been defined, all the rest almost certainly have.
+ */
+#ifndef STDIN_FILENO
+# define STDIN_FILENO  0
+# define STDOUT_FILENO 1
+# define STDERR_FILENO 2
+#endif
+
+/*
+ * C99 requires va_copy.  Older versions of GCC provide __va_copy.  Per the
+ * Autoconf manual, memcpy is a generally portable fallback.
+ */
+#ifndef va_copy
+# ifdef __va_copy
+#  define va_copy(d, s) __va_copy((d), (s))
+# else
+#  define va_copy(d, s) memcpy(&(d), &(s), sizeof(va_list))
+# endif
+#endif
+
+#endif /* !PORTABLE_SYSTEM_H */
index 6f639e4da11e71548fbe4cca4394ba5a58f00b76..1d2d32b25446a803fa8e2d9728feda4dd21f6650 100755 (executable)
@@ -29,7 +29,7 @@ use autodie;
 use strict;
 use warnings;
 
-use Test::More tests => 2;
+use Test::More tests => 4;
 
 BEGIN {
     use_ok('Authen::Kerberos');
@@ -42,3 +42,8 @@ local $ENV{KRB5_CONFIG} = 't/data/krb5.conf';
 # Test creation of a Kerberos context (Authen::Kerberos object).
 my $krb5 = Authen::Kerberos->new;
 isa_ok($krb5, 'Authen::Kerberos');
+
+# Create a principal (Authen::Kerberos::Principal object).
+my $principal = $krb5->principal('test@EXAMPLE.COM');
+isa_ok($principal, 'Authen::Kerberos::Principal');
+is("$principal", 'test@EXAMPLE.COM', 'Principal is correct');
diff --git a/typemap b/typemap
index d92bbc0c35f3b42ff92056eb8daad5fb3d62aa8c..9b834c46ca111595d9708245bb20eca29e729d8b 100644 (file)
--- a/typemap
+++ b/typemap
 
 TYPEMAP
 
+krb5_kvno                       T_IV
+krb5_timestamp                  T_IV
+
 Authen::Kerberos                T_PTROBJ_NU
 Authen::Kerberos::Kadmin        T_PTROBJ_NU
+Authen::Kerberos::Keytab        T_PTROBJ_NU
+Authen::Kerberos::KeytabEntry   T_PTROBJ_NU
+Authen::Kerberos::Principal     T_PTROBJ_NU
 
 INPUT
 
diff --git a/util/context.c b/util/context.c
new file mode 100644 (file)
index 0000000..428be1e
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Utility functions for XS code to manipulate Kerberos contexts.
+ *
+ * Provides helper functions to manipulate Kerberos contexts in the
+ * representation used inside Authen::Kerberos objects.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2014
+ *     The Board of Trustees of the Leland Stanford Junior University
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <EXTERN.h>
+#include <perl.h>
+#include <XSUB.h>
+
+#include <portable/krb5.h>
+
+
+/*
+ * Given an SV containing a krb5_context, return the underlying context
+ * pointer for use with direct Kerberos calls.  Takes the type of object from
+ * which the context is being retrieved for error reporting.
+ */
+krb5_context
+krb5_context_from_sv(SV *ctx_sv, const char *type)
+{
+    IV ctx_iv;
+    krb5_context ctx;
+
+    if (ctx_sv == NULL)
+        croak("no Kerberos context in %s object", type);
+    ctx_iv = SvIV(ctx_sv);
+    ctx = INT2PTR(krb5_context, ctx_iv);
+    return ctx;
+}
index 5df70fd96a1202d4b04221426dabfedf19883af8..292f0c90488f54a83eb0852dbc0bb2bc46131497 100644 (file)
@@ -32,7 +32,7 @@
 #include <perl.h>
 #include <XSUB.h>
 
-#include <krb5.h>
+#include <portable/krb5.h>
 
 
 /*
index aa9ac6c6571722242805f59194928c7fd1fd8447..9ef1c9be2a2d221e5f2564210c3145cb2bfa92fc 100644 (file)
@@ -32,7 +32,7 @@
 
 #include <portable/macros.h>
 
-#include <krb5.h>               /* krb5_contxt, krb5_error_code */
+#include <portable/krb5.h>      /* krb5_contxt, krb5_error_code */
 #include <perl.h>               /* bool */
 
 /* Used to check that an object argument to a function is not NULL. */
@@ -48,6 +48,13 @@ BEGIN_DECLS
 /* Default to a hidden visibility for all util functions. */
 #pragma GCC visibility push(hidden)
 
+/*
+ * Given an SV that represents a Kerberos context, returns the underlying
+ * context.  Takes the type of the object making this call for error
+ * reporting.  Croaks if the SV is not valid.
+ */
+krb5_context krb5_context_from_sv(SV *, const char *type);
+
 /*
  * Given a Kerberos context, an error code, and the Kerberos function that
  * failed, construct an Authen::Kerberos::Exception object and throw it using