diff options
| -rw-r--r-- | Makefile.am | 5 | ||||
| -rw-r--r-- | TODO | 13 | ||||
| -rw-r--r-- | client/internal.h | 55 | ||||
| -rw-r--r-- | client/srvtab.c | 169 | ||||
| -rw-r--r-- | client/wallet.c | 18 | ||||
| -rw-r--r-- | client/wallet.pod | 85 | ||||
| -rw-r--r-- | configure.ac | 6 | ||||
| -rwxr-xr-x | server/wallet-backend | 39 | ||||
| -rw-r--r-- | tests/client/basic-t.in | 46 | ||||
| -rwxr-xr-x | tests/data/cmd-fake | 13 | ||||
| -rw-r--r-- | tests/data/fake-data | bin | 0 -> 62 bytes | |||
| -rw-r--r-- | tests/data/fake-keytab | bin | 62 -> 334 bytes | |||
| -rw-r--r-- | tests/data/fake-srvtab | bin | 0 -> 47 bytes | 
13 files changed, 414 insertions, 35 deletions
diff --git a/Makefile.am b/Makefile.am index 0055bde..4087d79 100644 --- a/Makefile.am +++ b/Makefile.am @@ -20,10 +20,11 @@ EXTRA_DIST = TODO client/wallet.pod docs/design docs/design-acl \  bin_PROGRAMS = client/wallet  sbin_PROGRAMS = kasetkey/kasetkey  sbin_SCRIPTS = server/keytab-backend server/wallet-backend -client_wallet_SOURCES = client/wallet.c system.h +client_wallet_SOURCES = client/internal.h client/srvtab.c client/wallet.c \ +	system.h  client_wallet_CPPFLAGS = @REMCTL_CPPFLAGS@  client_wallet_LDFLAGS = @REMCTL_LDFLAGS@ -client_wallet_LDADD = -lremctl +client_wallet_LDADD = -lremctl -lkrb5  kasetkey_kasetkey_CPPFLAGS = @AFS_CPPFLAGS@  kasetkey_kasetkey_LDFLAGS = @AFS_LDFLAGS@  kasetkey_kasetkey_LDADD = @AFS_LIBS@ -lkrb4 @@ -2,12 +2,13 @@  Required to replace leland_srvtab: +* The wallet client should automatically set the sync attribute when +  called with -S. +  * Add support for limiting the enctypes of created keytabs by setting the    enctype attribute on the object and include the enctypes in the object    show display. -* Implement creation of srvtabs from keytabs in the wallet client. -  * Write new files atomically in the wallet client and save backups unless    told not to (write to file.new, link the old file to file.old, and do    an atomic rename). @@ -49,8 +50,16 @@ Future work:  * Add a help function to wallet-backend listing the commands. +* Rewrite the client test suite to use Perl and to make better use of +  shared code so that it can be broken into function components. + +* Stop hard-coding library names in the build system for kasetkey and +  wallet. +  * Add a test suite for kasetkey. +* Use standard error handling routines in the wallet client. +  * Write a conventions document for ACL naming, object naming, and similar    issues. diff --git a/client/internal.h b/client/internal.h new file mode 100644 index 0000000..834ec57 --- /dev/null +++ b/client/internal.h @@ -0,0 +1,55 @@ +/*  $Id$ +** +**  Internal support functions for the wallet client. +** +**  Written by Russ Allbery <rra@stanford.edu> +**  Copyright 2007 Board of Trustees, Leland Stanford Jr. University +** +**  See README for licensing terms. +*/ + +#ifndef CLIENT_INTERNAL_H +#define CLIENT_INTERNAL_H 1 + +/* __attribute__ is available in gcc 2.5 and later, but only with gcc 2.7 +   could you use the __format__ form of the attributes, which is what we use +   (to avoid confusion with other macros). */ +#ifndef __attribute__ +# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 7) +#  define __attribute__(spec)   /* empty */ +# endif +#endif + +/* Used for unused parameters to silence gcc warnings. */ +#define UNUSED  __attribute__((__unused__)) + +/* BEGIN_DECLS is used at the beginning of declarations so that C++ +   compilers don't mangle their names.  END_DECLS is used at the end. */ +#undef BEGIN_DECLS +#undef END_DECLS +#ifdef __cplusplus +# define BEGIN_DECLS    extern "C" { +# define END_DECLS      } +#else +# define BEGIN_DECLS    /* empty */ +# define END_DECLS      /* empty */ +#endif + +/* Temporary until we have some real configuration. */ +#ifndef SERVER +# define SERVER "wallet.stanford.edu" +#endif +#ifndef PORT +# define PORT 4444 +#endif + +BEGIN_DECLS + +/* Given a srvtab file, the Kerberos v5 principal, and the keytab file, write +   a srvtab file for the corresponding Kerberos v4 principal. */ +void write_srvtab(const char *srvtab, const char *principal, +                  const char *keytab); + +END_DECLS + +#endif /* !CLIENT_INTERNAL_H */ diff --git a/client/srvtab.c b/client/srvtab.c new file mode 100644 index 0000000..573840a --- /dev/null +++ b/client/srvtab.c @@ -0,0 +1,169 @@ +/*  $Id$ +** +**  Implementation of srvtab handling for the wallet client. +** +**  Written by Russ Allbery <rra@stanford.edu> +**  Copyright 2007 Board of Trustees, Leland Stanford Jr. University +** +**  See README for licensing terms. +*/ + +#include <config.h> +#include <system.h> + +#include <errno.h> +#include <fcntl.h> +#include <krb5.h> +#include <string.h> +#include <unistd.h> + +#include <client/internal.h> + +#ifndef KRB5_KRB4_COMPAT +# define ANAME_SZ 40 +# define INST_SZ  40 +# define REALM_SZ 40 +#endif + +#ifdef HAVE_KRB5_GET_ERROR_MESSAGE +static const char * +strerror_krb5(krb5_context ctx, krb5_error_code code) +{ +    const char *msg; + +    msg = krb5_get_error_message(ctx, code); +    if (msg == NULL) +        return "unknown error"; +    else +        return msg; +} +#elif HAVE_KRB5_GET_ERR_TEXT +static const char * +strerror_krb5(krb5_context ctx, krb5_error_code code) +{ +    return krb5_get_err_text(ctx, code); +} +#else /* !HAVE_KRB5_GET_ERROR_MESSAGE */ +static const char * +strerror_krb5(krb5_context ctx UNUSED, krb5_error_code code) +{ +    return error_message(code); +} +#endif + +#ifdef HAVE_KRB5_FREE_ERROR_MESSAGE +static void +strerror_krb5_free(krb5_context ctx, const char *msg) +{ +    krb5_free_error_message(ctx, msg); +} +#else /* !HAVE_KRB5_FREE_ERROR_MESSAGE */ +static void +strerror_krb5_free(krb5_context ctx UNUSED, const char *msg UNUSED) +{ +    return; +} +#endif /* !HAVE_KRB5_FREE_ERROR_MESSAGE */ + + +/* +**  Report a Kerberos error and exit. +*/ +static void +die_krb5(krb5_context ctx, const char *message, krb5_error_code code) +{ +    const char *k5_msg = NULL; + +    k5_msg = strerror_krb5(ctx, code); +    fprintf(stderr, "%s: %s\n", message, k5_msg); +    strerror_krb5_free(ctx, k5_msg); +    exit(1); +} + + +/* +**  Given the srvtab file name, a Kerberos principal (as a string), and a +**  keytab file name, extract the des-cbc-crc key from that keytab and write +**  it to the newly created srvtab file as a srvtab.  Convert the principal +**  from Kerberos v5 form to Kerberos v4 form. +** +**  We always force the kvno to 0 for the srvtab.  This works with how the +**  wallet synchronizes keys, even though it's not particularly correct. +** +**  On any failure, print an error message to standard error and then exit. +*/ +void +write_srvtab(const char *srvtab, const char *principal, const char *keytab) +{ +    krb5_context ctx = NULL; +    krb5_keytab kt; +    krb5_principal princ; +    krb5_keytab_entry entry; +    krb5_error_code ret; +    size_t length; +    int fd; +    ssize_t status; +    char aname[ANAME_SZ + 1] = ""; +    char inst[INST_SZ + 1]   = ""; +    char realm[REALM_SZ + 1] = ""; +    char data[ANAME_SZ + 1 + INST_SZ + 1 + REALM_SZ + 1 + 1 + 8]; + +    /* Open the keytab and get the DES key. */ +    ret = krb5_init_context(&ctx); +    if (ret != 0) +        die_krb5(ctx, "error creating Kerberos context", ret); +    ret = krb5_parse_name(ctx, principal, &princ); +    if (ret != 0) +        die_krb5(ctx, "error parsing Kerberos principal", ret); +    ret = krb5_kt_resolve(ctx, keytab, &kt); +    if (ret != 0) +        die_krb5(ctx, "error opening keytab", ret); +    ret = krb5_kt_get_entry(ctx, kt, princ, 0, ENCTYPE_DES_CBC_CRC, &entry); +    if (ret != 0) +        die_krb5(ctx, "error reading DES key from keytab", ret); +    if (entry.key.length != 8) { +        fprintf(stderr, "invalid DES key length in keytab\n"); +        exit(1); +    } +    krb5_kt_close(ctx, kt); + +    /* Convert the principal to a Kerberos v4 principal. */ +    ret = krb5_524_conv_principal(ctx, princ, aname, inst, realm); +    if (ret != 0) +        die_krb5(ctx, "error converting principal to Kerberos v4", ret); + +    /* Assemble the srvtab data. */ +    length = 0; +    strcpy(data + length, aname); +    length += strlen(aname); +    data[length++] = '\0'; +    strcpy(data + length, inst); +    length += strlen(inst); +    data[length++] = '\0'; +    strcpy(data + length, realm); +    length += strlen(realm); +    data[length++] = '\0'; +    data[length++] = '\0'; +    memcpy(data + length, entry.key.contents, 8); +    length += 8; +    krb5_free_keytab_entry_contents(ctx, &entry); + +    /* Write out the srvtab file. */ +    fd = open(srvtab, O_WRONLY | O_CREAT | O_TRUNC, 0600); +    if (fd < 0) { +        fprintf(stderr, "open of %s failed: %s", srvtab, strerror(errno)); +        exit(1); +    } +    status = write(fd, data, length); +    if (status < 0) { +        fprintf(stderr, "write to %s failed: %s", srvtab, strerror(errno)); +        exit(1); +    } else if (status != (ssize_t) length) { +        fprintf(stderr, "write to %s truncated", srvtab); +        exit(1); +    } +    if (close(fd) < 0) { +        fprintf(stderr, "close of %s failed: %s", srvtab, strerror(errno)); +        exit(1); +    } +} diff --git a/client/wallet.c b/client/wallet.c index 304856c..8d8bb58 100644 --- a/client/wallet.c +++ b/client/wallet.c @@ -13,16 +13,9 @@  #include <errno.h>  #include <fcntl.h> -  #include <remctl.h> -/* Temporary until we have some real configuration. */ -#ifndef SERVER -# define SERVER "wallet.stanford.edu" -#endif -#ifndef PORT -# define PORT 4444 -#endif +#include <client/internal.h>  /* Usage message. */  static const char usage_message[] = "\ @@ -120,11 +113,16 @@ main(int argc, char *argv[])          fprintf(stderr, "wallet: -f only supported for get\n");          exit(1);      } -    if (srvtab != NULL) +    if (srvtab != NULL) {          if (strcmp(argv[0], "get") != 0 || strcmp(argv[1], "keytab") != 0) {              fprintf(stderr, "wallet: -S only supported for get keytab\n");              exit(1);          } +        if (file == NULL) { +            fprintf(stderr, "wallet: -S requires -f\n"); +            exit(1); +        } +    }      /* Allocate space for the command to send to the server. */      command = malloc(sizeof(char *) * (argc + 2)); @@ -169,6 +167,8 @@ main(int argc, char *argv[])              fprintf(stderr, "close of %s failed: %s", file, strerror(errno));              exit(1);          } +        if (srvtab != NULL) +            write_srvtab(srvtab, command[3], file);      } else {          fwrite(result->stdout_buf, 1, result->stdout_len, stdout);      } diff --git a/client/wallet.pod b/client/wallet.pod index 3f7c60b..e7ea4a0 100644 --- a/client/wallet.pod +++ b/client/wallet.pod @@ -5,8 +5,8 @@ wallet - Client for retrieving secure data from a central server  =head1 SYNOPSIS  B<wallet> [B<-hv>] [B<-c> I<command>] [B<-f> I<output>] -[B<-k> I<principal>] [B<-p> I<port>] [B<-s> I<server>] I<command> -[I<arg> ...] +[B<-k> I<principal>] [B<-p> I<port>] [B<-s> I<server>] [B<-S> I<srvtab>] +I<command> [I<arg> ...]  =head1 DESCRIPTION @@ -36,16 +36,17 @@ C<keytab> and a name of C<host/example.com>.  The meaning of the name is  specific to each type of object.  Most other wallet commands besides those three are only available to -wallet administrators.  The other commands allow setting ownership and -ACLs on objects, creating and destroying objects, creating and destroying -ACLs, and adding and removing entries from ACLs.  An ACL consists of one -or more entries, each of which is a scheme and an identifier.  A scheme -specifies a way of checking whether a user is authorized.  An identifier -is some data specific to the scheme that specifies which users are -authorized.  For example, for the C<krb5> scheme, the identifier is a -principal name and only that principal is authorized by that ACL entry. -For the C<pts> scheme, the identifier is a PTS group name, and all members -of that PTS group are authorized by that ACL entry. +wallet administrators.  The exception is attribute commands; see +L<ATTRIBUTES>.  The other commands allow setting ownership and ACLs on +objects, creating and destroying objects, creating and destroying ACLs, +and adding and removing entries from ACLs.  An ACL consists of one or more +entries, each of which is a scheme and an identifier.  A scheme specifies +a way of checking whether a user is authorized.  An identifier is some +data specific to the scheme that specifies which users are authorized. +For example, for the C<krb5> scheme, the identifier is a principal name +and only that principal is authorized by that ACL entry.  For the C<pts> +scheme, the identifier is a PTS group name, and all members of that PTS +group are authorized by that ACL entry.  To run the wallet command-line client, you must already have a Kerberos  ticket.  You can obtain a Kerberos ticket with B<kinit> and see your @@ -86,6 +87,17 @@ commands are ignored.  The port to connect to on the wallet server.  The default is the default  remctl port (4444). +=item B<-S> I<srvtab> + +This flag is only used in combination with the C<get> command on a +C<keytab> object, and must be used in conjunction with the B<-f> flag. +After the keytab is saved to the file specified by B<-f>, the DES key for +that principal will be extracted and written as a Kerberos v4 srvtab to +the file I<srvtab>.  Any existing contents of I<srvtab> will be +destroyed.  For more information on how the principal is converted to +Kerberos v4, see the description of the B<sync> attribute under +L<ATTRIBUTES>. +  =item B<-s> I<server>  The wallet server to connect to.  The default is a hard-coded server value @@ -118,6 +130,8 @@ object that change data except the C<flags> commands, nor can the C<get>  command be used on that object.  C<show>, C<getacl>, and C<owner> or  C<expires> without an argument can still be used on that object. +For more information on attributes, see L<ATTRIBUTES>. +  =over 4  =item acl add <id> <scheme> <identifier> @@ -240,8 +254,6 @@ particular object type, and <attr> must be an attribute type known to the  underlying object implementation.  To clear the attribute for this object,  pass in a <value> of the empty string (C<''>). -Currently, no object attributes are implemented. -  =item show <type> <name>  Displays the current object metadata for the object identified by <type> @@ -262,9 +274,52 @@ will be lifted in the future.  =back +=head1 ATTRIBUTES + +Object attributes store additional properties and configuration +information for objects stored in the wallet.  They are displayed as part +of the object data with C<show>, retrieved with C<getattr>, and set with +C<setattr>. + +=head1 Keytab Attributes + +Keytab objects support the following attributes: + +=over 4 + +=item sync + +Sets the external systems to which the key of a given principal is +synchronized.  The only supported value for this attribute is C<kaserver>, +which says to synchronize the key with an AFS Kerberos v4 kaserver. + +If this attribute is set on a keytab, whenever the C<get> command is run +for that keytab, the DES key will be extracted from that keytab and set in +the configured AFS kaserver.  If the B<-S> option is given to the +B<wallet> client, the srvtab corresponding to the keytab will be written +to the file specified with that option.  The Kerberos v4 principal name +will be the same as the Kerberos v5 principal name except that the +components are separated by C<.> instead of C</>; the second component is +truncated after the first C<.> if the first component is one of C<host>, +C<ident>, C<imap>, C<pop>, or C<smtp>; and the first component is C<rcmd> +if the Kerberos v5 principal component is C<host>.  The principal name +must not contain more than two components. + +If this attribute is set, calling C<destroy> will also destroy the +principal from the AFS kaserver, with a principal mapping determined as +above. + +The realm of the srvtab defaults to the same realm as the keytab.  You can +change this by setting the v4_realm configuration option in the [realms] +section of krb5.conf for the local realm.  The keytab must be for a +principal in the default local realm for the B<-S> option to work +correctly. + +=back +  =head1 SEE ALSO -remctl(1), remctld(8) +krb5.conf(5), remctl(1), remctld(8)  This program is part of the wallet system.  The current version is available  from L<http://www.eyrie.org/~eagle/software/wallet/>. diff --git a/configure.ac b/configure.ac index 700970e..2a8361f 100644 --- a/configure.ac +++ b/configure.ac @@ -71,6 +71,12 @@ AC_SUBST([AFS_LIBS])  AC_CHECK_HEADERS([kerberosIV/krb.h])  AC_CHECK_DECLS([ubik_Call], , , [#include <ubik.h>]) +save_LIBS=$LIBS +LIBS=-lkrb5 +AC_CHECK_FUNCS([krb5_get_error_message \ +    krb5_free_error_message \ +    krb5_get_err_text]) +LIBS=$save_LIBS  AC_CONFIG_HEADER([config.h])  AC_CONFIG_FILES([Makefile perl/Makefile.PL]) diff --git a/server/wallet-backend b/server/wallet-backend index 2ab3daf..b6c0dfb 100755 --- a/server/wallet-backend +++ b/server/wallet-backend @@ -238,6 +238,8 @@ object that change data except the C<flags> commands, nor can the C<get>  command be used on that object.  C<show>, C<getacl>, and C<owner> or  C<expires> without an argument can still be used on that object. +For more information on attributes, see L<ATTRIBUTES>. +  =over 4  =item acl add <id> <scheme> <identifier> @@ -359,8 +361,6 @@ particular object type, and <attr> must be an attribute type known to the  underlying object implementation.  To clear the attribute for this object,  pass in a <value> of the empty string (C<''>). -Currently, no object attributes are implemented. -  =item show <type> <name>  Displays the current object metadata for the object identified by <type> @@ -381,6 +381,41 @@ will be lifted in the future.  =back +=head1 ATTRIBUTES + +Object attributes store additional properties and configuration +information for objects stored in the wallet.  They are displayed as part +of the object data with C<show>, retrieved with C<getattr>, and set with +C<setattr>. + +=head1 Keytab Attributes + +Keytab objects support the following attributes: + +=over 4 + +=item sync + +Sets the external systems to which the key of a given principal is +synchronized.  The only supported value for this attribute is C<kaserver>, +which says to synchronize the key with an AFS Kerberos v4 kaserver. + +If this attribute is set on a keytab, whenever the C<get> command is run for +that keytab, the DES key will be extracted from that keytab and set in the +configured AFS kaserver.  The Kerberos v4 principal name will be the same as +the Kerberos v5 principal name except that the components are separated by +C<.> instead of C</>; the second component is truncated after the first C<.> +if the first component is one of C<host>, C<ident>, C<imap>, C<pop>, or +C<smtp>; and the first component is C<rcmd> if the Kerberos v5 principal +component is C<host>.  The principal name must not contain more than two +components. + +If this attribute is set, calling C<destroy> will also destroy the +principal from the AFS kaserver, with a principal mapping determined as +above. + +=back +  =head1 SEE ALSO  Wallet::Server(3), remctld(8) diff --git a/tests/client/basic-t.in b/tests/client/basic-t.in index f088933..50a4ab5 100644 --- a/tests/client/basic-t.in +++ b/tests/client/basic-t.in @@ -54,7 +54,7 @@ runfailure () {  }  # Print the number of tests. -echo 7 +echo 10  # Find the client program.  if [ -f ../data/test.keytab ] ; then @@ -65,7 +65,7 @@ else      fi  fi  if [ ! -f data/test.keytab ] || [ -z "@REMCTLD@" ] ; then -    for n in 1 2 3 4 5 6 7 ; do +    for n in 1 2 3 4 5 6 7 8 9 10 ; do          echo ok $n \# skip -- no Kerberos configuration      done      exit 0 @@ -98,14 +98,56 @@ if [ ! -f data/pid ] ; then      exit 1  fi +# We need a modified krb5.conf file for the srvtab test to work, since we need +# to add a v4_realm setting for the test-k5.stanford.edu realm that the keytab +# is for. +krb5conf= +for p in /etc/krb5.conf /usr/local/etc/krb5.conf ; do +    if [ -r "$p" ] ; then +        krb5conf="$p" +        sed -e '/^ *test-k5.stanford.edu =/,/}/d' \ +            -e 's/\(default_realm.*=\) .*/\1 test-k5.stanford.edu/' \ +            "$p" > ./krb5.conf +        cat >> krb5.conf <<EOF + +[realms] +    test-k5.stanford.edu = { +        v4_realm = TEST.STANFORD.EDU +    } +EOF +        KRB5_CONFIG="./krb5.conf" +        export KRB5_CONFIG +        break +    fi +done +  # Now, we can finally run our tests.  runsuccess "" -c fake-wallet get keytab -f keytab service/fake-test +if cmp keytab data/fake-data >/dev/null 2>&1 ; then +    printcount "ok" +    rm keytab +else +    printcount "not ok" +fi +runsuccess "" -c fake-wallet get keytab -f keytab -S srvtab service/fake-srvtab  if cmp keytab data/fake-keytab >/dev/null 2>&1 ; then      printcount "ok"      rm keytab  else      printcount "not ok"  fi +if [ -n "$krb5conf" ] ; then +    if cmp srvtab data/fake-srvtab >/dev/null 2>&1 ; then +        printcount "ok" +        rm srvtab +    else +        printcount "not ok" +    fi +    KRB5_CONFIG= +    rm krb5.conf +else +    printcount "ok # skip cannot find krb5.conf" +fi  runsuccess "Some stuff about service/fake-test" \      -c fake-wallet show keytab service/fake-test  runfailure 1 "wallet: Unknown object type srvtab" \ diff --git a/tests/data/cmd-fake b/tests/data/cmd-fake index 83e3e0a..16d4b3a 100755 --- a/tests/data/cmd-fake +++ b/tests/data/cmd-fake @@ -19,13 +19,20 @@ fi  case "$command" in  get) -    if [ "$1" = "service/fake-test" ] ; then +    case "$1" in +    service/fake-test) +        cat data/fake-data +        exit 0 +        ;; +    service/fake-srvtab)          cat data/fake-keytab          exit 0 -    else +        ;; +    *)          echo "Unknown keytab $1" >&2          exit 1 -    fi +        ;; +    esac      ;;  show)      if [ "$1" = "service/fake-test" ] ; then diff --git a/tests/data/fake-data b/tests/data/fake-data Binary files differnew file mode 100644 index 0000000..92e3caa --- /dev/null +++ b/tests/data/fake-data diff --git a/tests/data/fake-keytab b/tests/data/fake-keytab Binary files differindex 92e3caa..714d9b6 100644 --- a/tests/data/fake-keytab +++ b/tests/data/fake-keytab diff --git a/tests/data/fake-srvtab b/tests/data/fake-srvtab Binary files differnew file mode 100644 index 0000000..3c0ec65 --- /dev/null +++ b/tests/data/fake-srvtab  | 
