diff options
| -rw-r--r-- | Makefile.am | 5 | ||||
| -rw-r--r-- | NEWS | 4 | ||||
| -rw-r--r-- | README | 18 | ||||
| -rw-r--r-- | TODO | 25 | ||||
| -rw-r--r-- | client/error.c | 121 | ||||
| -rw-r--r-- | client/internal.h | 30 | ||||
| -rw-r--r-- | client/keytab.c | 14 | ||||
| -rw-r--r-- | client/srvtab.c | 87 | ||||
| -rw-r--r-- | client/wallet.c | 117 | ||||
| -rw-r--r-- | client/wallet.pod | 67 | ||||
| -rw-r--r-- | configure.ac | 27 | ||||
| -rw-r--r-- | tests/client/basic-t.in | 165 | ||||
| -rw-r--r-- | tests/data/README | 7 | ||||
| -rw-r--r-- | tests/libtest.sh | 82 | 
14 files changed, 525 insertions, 244 deletions
| diff --git a/Makefile.am b/Makefile.am index c5a8f6c..fdbf86d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -32,8 +32,9 @@ util_libutil_a_SOURCES = util/concat.c util/messages.c util/util.h \  bin_PROGRAMS = client/wallet  dist_sbin_SCRIPTS = server/keytab-backend server/wallet-backend -client_wallet_SOURCES = client/file.c client/internal.h client/keytab.c \ -	client/remctl.c client/srvtab.c client/wallet.c system.h +client_wallet_SOURCES = client/error.c client/file.c client/internal.h \ +	client/keytab.c client/remctl.c client/srvtab.c client/wallet.c \ +	system.h  client_wallet_CPPFLAGS = $(REMCTL_CPPFLAGS)  client_wallet_LDFLAGS = $(REMCTL_LDFLAGS)  client_wallet_LDADD = util/libutil.a portable/libportable.a $(REMCTL_LIBS) \ @@ -2,6 +2,10 @@  wallet 0.6 (unreleased) +    The wallet client can now get the server, port, principal, and remctl +    type from krb5.conf as well as from compile-time defaults and +    command-line options. +      The build system now probes for GSS-API, Kerberos v5 and v4, and AFS      libraries as necessary rather than hard-coding libraries.  Building      on systems without strong shared library dependencies and building @@ -136,14 +136,18 @@ BUILD AND INSTALLATION    You can build and install wallet with the standard commands: -      ./configure --with-wallet-server=<server> +      ./configure        make        make install    The last step will probably have to be done as root.  Currently, this -  always installs both the client and the server.  You must specify the -  default wallet server at configure time unless you're at Stanford.  This -  will be replaced with real client configuration in a later release. +  always installs both the client and the server. + +  You can pass the --with-wallet-server and --with-wallet-port options to +  configure to compile in a default wallet server and port.  If no port is +  set, the remctl default port is used.  If no server is set, the server +  must be specified either in krb5.conf configuration or on the wallet +  command line or the client will exit with an error.    By default, wallet installs itself under /usr/local except for the    server Perl modules, which are installed into whatever default site @@ -250,9 +254,9 @@ CONFIGURATION    configuration on your KDC if you want unchanging flag support, and set    up a srvtab if you want AFS kaserver synchronization support. -  The client currently has no configuration options and hard-codes the -  wallet server and port at compile time.  This will change in a future -  release. +  The wallet client supports reading configuration settings from the +  system krb5.conf file.  For more information, see the CONFIGURATION +  section of the wallet client man page (man wallet).  THANKS @@ -41,9 +41,6 @@ Release 1.0:    -u option similar to leland_srvtab.  Needs good error messages on    Kerberos failures. -* The wallet client should read configuration information from krb5.conf, -  and possibly from some other configuration as well. -  * Error messages from ACL operations should refer to the ACLs by name    instead of by ID. @@ -101,6 +98,17 @@ Future work:  * Add a comment field for objects that can be set by the owner. +* The keytab backend currently only supports MIT Kerberos.  Add support +  for Heimdal.  This should probably be done by writing a separate class +  that handles the kadmin operations that can be subclassed and that +  dynamically chooses its implementation based on run-time configuration. + +* When reading configuration from krb5.conf, we should first try to +  determine our principal from any existing K5 ticket cache (after +  obtaining tickets if -u was given) and extract the realm from that +  principal, using it as the default realm when reading configuration +  information. +  * Implement an ssh keypair wallet object.  The server can run ssh-keygen    to generate a public/private key pair and return both to the client,    which would split them apart.  Used primarily for host keys.  May need a @@ -123,9 +131,6 @@ Future work:  * Add support for enforcing a naming policy through another policy    function. -* The keytab backend currently only supports MIT Kerberos.  Add support -  for Heimdal. -  * Add readline support to the wallet client to make it easier to issue    multiple commands. @@ -152,10 +157,6 @@ Future work:  * The Wallet::Config class is very ugly and could use some better internal    API to reference the variables in it. -* Consider using Class::Accessor to get rid of the scaffolding code to -  access object data, and a Wallet::Base class to handle things like the -  error() method common to many classes. -  * Use Class::DBI and Class::Trigger to handle the data access layer rather    than writing SQL directly, and implement the logging requirements with    triggers rather than explicit SQL.  This may also replace @@ -163,5 +164,9 @@ Future work:  May or may not be good ideas: +* Consider using Class::Accessor to get rid of the scaffolding code to +  access object data, and a Wallet::Base class to handle things like the +  error() method common to many classes. +  * Remove the hard-coded ADMIN ACL in the server with something more    configurable, perhaps a global ACL table or something. diff --git a/client/error.c b/client/error.c new file mode 100644 index 0000000..e95b284 --- /dev/null +++ b/client/error.c @@ -0,0 +1,121 @@ +/*  $Id$ +** +**  Error handling for the wallet client. +** +**  Provides versions of die and warn that take a Kerberos context and a +**  Kerberos error code and append the Kerberos error message to the provided +**  formatted message. +** +**  Written by Russ Allbery <rra@stanford.edu> +**  Copyright 2006, 2007, 2008 +**      Board of Trustees, Leland Stanford Jr. University +** +**  See LICENSE for licensing terms. +*/ + +#include <config.h> + +#include <krb5.h> +#include <stdio.h> +#include <sys/types.h> + +#include <krb5.h> +#if !defined(HAVE_KRB5_GET_ERROR_MESSAGE) && !defined(HAVE_KRB5_GET_ERR_TEXT) +# if defined(HAVE_IBM_SVC_KRB5_SVC_H) +#  include <ibm_svc/krb5_svc.h> +# elif defined(HAVE_ET_COM_ERR_H) +#  include <et/com_err.h> +# else +#  include <com_err.h> +# endif +#endif + +#include <client/internal.h> +#include <util/util.h> + +/* This string is returned for unknown error messages.  We use a static +   variable so that we can be sure not to free it. */ +static const char error_unknown[] = "unknown error"; + + +/* +**  Given a Kerberos error code, return the corresponding error.  Prefer the +**  Kerberos interface if available since it will provide context-specific +**  error information, whereas the error_message() call will only provide a +**  fixed message. +*/ +static const char * +get_error(krb5_context ctx, krb5_error_code code) +{ +    const char *msg = NULL; + +#if defined(HAVE_KRB5_GET_ERROR_MESSAGE) +    msg = krb5_get_error_message(ctx, code); +#elif defined(HAVE_KRB5_GET_ERR_TEXT) +    msg = krb5_get_err_text(ctx, code); +#elif defined(HAVE_KRB5_SVC_GET_MSG) +    krb5_svc_get_msg(code, &msg); +#else +    msg = error_message(code); +#endif +    if (msg == NULL) +        return error_unknown; +    else +        return msg; +} + + +/* +** Free an error string if necessary. +*/ +static void +free_error(krb5_context ctx, const char *msg) +{ +    if (msg == error_unknown) +        return; +#if defined(HAVE_KRB5_FREE_ERROR_MESSAGE) +    krb5_free_error_message(ctx, msg); +#elif defined(HAVE_KRB5_SVC_GET_MSG) +    krb5_free_string((char *) msg); +#endif +} + + +/* +**  Report a Kerberos error and exit. +*/ +void +die_krb5(krb5_context ctx,  krb5_error_code code, const char *format, ...) +{ +    const char *k5_msg = NULL; +    char *message; +    va_list args; + +    k5_msg = get_error(ctx, code); +    va_start(args, format); +    if (xasprintf(&message, format, args) < 0) +        die("internal error: unable to format error message"); +    va_end(args); +    die("%s: %s\n", message, k5_msg); +} + + +/* +**  Report a Kerberos error. +*/ +void +warn_krb5(krb5_context ctx,  krb5_error_code code, const char *format, ...) +{ +    const char *k5_msg = NULL; +    char *message; +    va_list args; + +    k5_msg = get_error(ctx, code); +    va_start(args, format); +    if (xasprintf(&message, format, args) < 0) +        die("internal error: unable to format error message"); +    va_end(args); +    warn("%s: %s\n", message, k5_msg); +    free(message); +    free_error(ctx, k5_msg); +} diff --git a/client/internal.h b/client/internal.h index 6f4a991..1dcb608 100644 --- a/client/internal.h +++ b/client/internal.h @@ -3,7 +3,7 @@  **  Internal support functions for the wallet client.  **  **  Written by Russ Allbery <rra@stanford.edu> -**  Copyright 2007 Board of Trustees, Leland Stanford Jr. University +**  Copyright 2007, 2008 Board of Trustees, Leland Stanford Jr. University  **  **  See LICENSE for licensing terms.  */ @@ -11,7 +11,9 @@  #ifndef CLIENT_INTERNAL_H  #define CLIENT_INTERNAL_H 1 +#include <krb5.h>  #include <sys/types.h> +  #include <util/util.h>  /* Forward declarations to avoid unnecessary includes. */ @@ -35,23 +37,31 @@ BEGIN_DECLS  int run_command(struct remctl *, const char **command, char **data,                  size_t *length); -/* Given a remctl object, the type for the wallet interface, the name of a -   keytab object, and a file name, call the correct wallet commands to -   download a keytab and write it to that file.  If srvtab is not NULL, write -   a srvtab based on the keytab after a successful download. */ -int get_keytab(struct remctl *, const char *type, const char *name, -               const char *file, const char *srvtab); +/* Given a remctl object, the Kerberos context, the type for the wallet +   interface, the name of a keytab object, and a file name, call the correct +   wallet commands to download a keytab and write it to that file.  If srvtab +   is not NULL, write a srvtab based on the keytab after a successful +   download. */ +int get_keytab(struct remctl *, krb5_context, const char *type, +               const char *name, const char *file, const char *srvtab);  /* Given a filename, some data, and a length, write that data to the given     file safely and atomically by creating file.new, writing the data, linking     file to file.bak, and then renaming file.new to file. */  void write_file(const char *name, const void *data, size_t length); -/* 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, +/* Given a Kerberos context, a srvtab file, the Kerberos v5 principal, and the +   keytab file, write a srvtab file for the corresponding Kerberos v4 +   principal. */ +void write_srvtab(krb5_context, const char *srvtab, const char *principal,                    const char *keytab); +/* Versions of die and warn that report Kerberos errors. */ +void die_krb5(krb5_context, krb5_error_code, const char *, ...) +    __attribute__((__noreturn__, __format__(printf, 3, 4))); +void warn_krb5(krb5_context, krb5_error_code, const char *, ...) +    __attribute__((__format__(printf, 3, 4))); +  END_DECLS  #endif /* !CLIENT_INTERNAL_H */ diff --git a/client/keytab.c b/client/keytab.c index 1137c45..04b34c9 100644 --- a/client/keytab.c +++ b/client/keytab.c @@ -3,7 +3,7 @@  **  Implementation of keytab handling for the wallet client.  **  **  Written by Russ Allbery <rra@stanford.edu> -**  Copyright 2007 Board of Trustees, Leland Stanford Jr. University +**  Copyright 2007, 2008 Board of Trustees, Leland Stanford Jr. University  **  **  See LICENSE for licensing terms.  */ @@ -51,13 +51,13 @@ set_sync(struct remctl *r, const char *type, const char *name)  /* -**  Given a remctl object, the name of a keytab object, and a file name, call -**  the correct wallet commands to download a keytab and write it to that -**  file.  Returns the setatus or 255 on an internal error. +**  Given a remctl object, the Kerberos context, the name of a keytab object, +**  and a file name, call the correct wallet commands to download a keytab and +**  write it to that file.  Returns the setatus or 255 on an internal error.  */  int -get_keytab(struct remctl *r, const char *type, const char *name, -           const char *file, const char *srvtab) +get_keytab(struct remctl *r, krb5_context ctx, const char *type, +           const char *name, const char *file, const char *srvtab)  {      const char *command[5];      char *data = NULL; @@ -81,6 +81,6 @@ get_keytab(struct remctl *r, const char *type, const char *name,      }      write_file(file, data, length);      if (srvtab != NULL) -        write_srvtab(srvtab, name, file); +        write_srvtab(ctx, srvtab, name, file);      return 0;  } diff --git a/client/srvtab.c b/client/srvtab.c index dd1cd58..b454720 100644 --- a/client/srvtab.c +++ b/client/srvtab.c @@ -3,7 +3,7 @@  **  Implementation of srvtab handling for the wallet client.  **  **  Written by Russ Allbery <rra@stanford.edu> -**  Copyright 2007 Board of Trustees, Leland Stanford Jr. University +**  Copyright 2007, 2008 Board of Trustees, Leland Stanford Jr. University  **  **  See LICENSE for licensing terms.  */ @@ -22,67 +22,12 @@  # 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); -    warn("%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. +**  Given the Kerberos context, 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. @@ -90,9 +35,9 @@ die_krb5(krb5_context ctx, const char *message, krb5_error_code code)  **  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) +write_srvtab(krb5_context ctx, const char *srvtab, const char *principal, +             const char *keytab)  { -    krb5_context ctx = NULL;      krb5_keytab kt;      krb5_principal princ;      krb5_keytab_entry entry; @@ -104,28 +49,24 @@ write_srvtab(const char *srvtab, const char *principal, const char *keytab)      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); +        die_krb5(ctx, ret, "error parsing Kerberos principal %s", principal);      ret = krb5_kt_resolve(ctx, keytab, &kt);      if (ret != 0) -        die_krb5(ctx, "error opening keytab", ret); +        die_krb5(ctx, ret, "error opening keytab %s", keytab);      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); -    } +        die_krb5(ctx, ret, "error reading DES key from keytab %s", keytab); +    if (entry.key.length != 8) +        die("invalid DES key length in keytab");      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); +        die_krb5(ctx, ret, "error converting principal %s to Kerberos v4", +                 principal);      /* Assemble the srvtab data. */      length = 0; diff --git a/client/wallet.c b/client/wallet.c index 92f2984..28d27e8 100644 --- a/client/wallet.c +++ b/client/wallet.c @@ -3,7 +3,8 @@  **  The client program for the wallet system.  **  **  Written by Russ Allbery <rra@stanford.edu> -**  Copyright 2006, 2007 Board of Trustees, Leland Stanford Jr. University +**  Copyright 2006, 2007, 2008 +**      Board of Trustees, Leland Stanford Jr. University  **  **  See LICENSE for licensing terms.  */ @@ -12,11 +13,31 @@  #include <system.h>  #include <errno.h> +#include <krb5.h>  #include <remctl.h>  #include <client/internal.h>  #include <util/util.h> +/* Basic wallet behavior options set either on the command line or via +   krb5.conf.  If set via krb5.conf, we allocate memory for the strings, but +   we never free them. */ +struct options { +    char *type; +    char *server; +    char *principal; +    int port; +}; + +/* Allow defaults to be set for a particular site with configure options if +   people don't want to use krb5.conf for some reason. */ +#ifndef WALLET_SERVER +# define WALLET_SERVER NULL +#endif +#ifndef WALLET_PORT +# define WALLET_PORT 0 +#endif +  /* Usage message.  Use as a format and pass the port number. */  static const char usage_message[] = "\  Usage: wallet [options] <command> <type> <name> [<arg> ...]\n\ @@ -45,18 +66,69 @@ usage(int status)  /* +**  Load a string option from Kerberos appdefaults.  This requires an annoying +**  workaround because one cannot specify a default value of NULL. +*/ +static void +default_string(krb5_context ctx, const char *opt, const char *defval, +               char **result) +{ +    if (defval == NULL) +        defval = ""; +    krb5_appdefault_string(ctx, "wallet", NULL, opt, defval, result); +    if (*result != NULL && (*result)[0] == '\0') { +        free(*result); +        *result = NULL; +    } +} + + +/* +**  Load a number option from Kerberos appdefaults.  The native interface +**  doesn't support numbers, so we actually read a string and then convert. +*/ +static void +default_number(krb5_context ctx, const char *opt, int defval, int *result) +{ +    char *tmp; + +    krb5_appdefault_string(ctx, "wallet", NULL, opt, "", &tmp); +    if (tmp != NULL && tmp[0] != '\0') +        *result = atoi(tmp); +    else +        *result = defval; +    if (tmp != NULL) +        free(tmp); +} + + +/* +**  Set option defaults and then get krb5.conf configuration, if any, and +**  override the defaults.  Later, command-line options will override those +**  defaults. +*/ +static void +set_defaults(krb5_context ctx, struct options *options) +{ +    default_string(ctx, "wallet_type", "wallet", &options->type); +    default_string(ctx, "wallet_server", WALLET_SERVER, &options->server); +    default_string(ctx, "wallet_principal", NULL, &options->principal); +    default_number(ctx, "wallet_port", WALLET_PORT, &options->port); +} + + +/*  **  Main routine.  Parse the arguments and then perform the desired  **  operation.  */  int  main(int argc, char *argv[])  { +    krb5_context ctx; +    krb5_error_code retval; +    struct options options;      int option, i, status;      const char **command; -    const char *type = "wallet"; -    const char *server = WALLET_SERVER; -    const char *principal = NULL; -    unsigned short port = WALLET_PORT;      const char *file = NULL;      const char *srvtab = NULL;      struct remctl *r; @@ -66,16 +138,22 @@ main(int argc, char *argv[])      /* Set up logging and identity. */      message_program_name = "wallet"; +    /* Initialize default configuration. */ +    retval = krb5_init_context(&ctx); +    if (retval != 0) +        die_krb5(ctx, retval, "cannot initialize Kerberos"); +    set_defaults(ctx, &options); +      while ((option = getopt(argc, argv, "c:f:k:hp:S:s:v")) != EOF) {          switch (option) {          case 'c': -            type = optarg; +            options.type = optarg;              break;          case 'f':              file = optarg;              break;          case 'k': -            principal = optarg; +            options.principal = optarg;              break;          case 'h':              usage(0); @@ -85,13 +163,13 @@ main(int argc, char *argv[])              tmp = strtol(optarg, &end, 10);              if (tmp <= 0 || tmp > 65535 || *end != '\0')                  die("invalid port number %s", optarg); -            port = tmp; +            options.port = tmp;              break;          case 'S':              srvtab = optarg;              break;          case 's': -            server = optarg; +            options.server = optarg;              break;          case 'v':              printf("%s\n", PACKAGE_STRING); @@ -117,11 +195,16 @@ main(int argc, char *argv[])              die("-S option requires -f also be used");      } +    /* If no server was set at configure time and none was set on the command +       line or with krb5.conf settings, we can't continue. */ +    if (options.server == NULL) +        die("no server specified in krb5.conf or with -s"); +      /* Open a remctl connection. */      r = remctl_new();      if (r == NULL)          sysdie("cannot allocate memory"); -    if (!remctl_open(r, server, port, principal)) +    if (!remctl_open(r, options.server, options.port, options.principal))          die("%s", remctl_error(r));      /* Most commands, we handle ourselves, but keytab get commands with -f are @@ -129,20 +212,16 @@ main(int argc, char *argv[])      if (strcmp(argv[0], "get") == 0 && strcmp(argv[1], "keytab") == 0) {          if (argc > 3)              die("too many arguments"); -        status = get_keytab(r, type, argv[2], file, srvtab); -        remctl_close(r); -        exit(status); +        status = get_keytab(r, ctx, options.type, argv[2], file, srvtab);      } else {          command = xmalloc(sizeof(char *) * (argc + 2)); -        command[0] = type; +        command[0] = options.type;          for (i = 0; i < argc; i++)              command[i + 1] = argv[i];          command[argc + 1] = NULL;          status = run_command(r, command, NULL, NULL); -        remctl_close(r); -        exit(status);      } - -    /* This should never be reached. */ -    die("invalid return from wallet server"); +    remctl_close(r); +    krb5_free_context(ctx); +    exit(status);  } diff --git a/client/wallet.pod b/client/wallet.pod index 8991123..c216cb7 100644 --- a/client/wallet.pod +++ b/client/wallet.pod @@ -62,7 +62,8 @@ protocol to talk to the wallet server.  The command prefix (remctl type) to use.  Normally this is an internal  implementation detail and the default (C<wallet>) should be fine.  It may  sometimes be useful to use a different prefix for testing a different -version of the wallet code on the server. +version of the wallet code on the server.  This option can also be set in +F<krb5.conf>; see L<CONFIGURATION> below.  =item B<-f> I<output> @@ -76,6 +77,7 @@ will be destroyed.  The service principal of the wallet server.  The default is to use the  C<host> principal for the wallet server.  The principal chosen must match  one of the keys in the keytab used by B<remctld> on the wallet server. +This option can also be set in F<krb5.conf>; see L<CONFIGURATION> below.  =item B<-h> @@ -85,7 +87,8 @@ commands are ignored.  =item B<-p> I<port>  The port to connect to on the wallet server.  The default is the default -remctl port. +remctl port.  This option can also be set in F<krb5.conf>; see +L<CONFIGURATION> below.  =item B<-S> I<srvtab> @@ -100,8 +103,9 @@ L<ATTRIBUTES>.  =item B<-s> I<server> -The wallet server to connect to.  The default is a hard-coded server value -determined at configure time when compiling the wallet client. +The wallet server to connect to.  The default may be set when compiling +the wallet client.  If it isn't, either B<-s> must be given or the server +must be set in F<krb5.conf>.  See L<CONFIGURATION> below.  =item B<-v> @@ -352,6 +356,61 @@ correctly.  =back +=head1 CONFIGURATION + +B<wallet> can optionally be configured in the system F<krb5.conf>.  It +will read the default F<krb5.conf> file for the Kerberos libraries with +which it was compiled.  To set an option, put the option in the +[appdefaults] section.  B<wallet> will look for options either at the top +level of the [appdefaults] section or in a subsection named C<wallet>, +inside or outside of a section for the realm.  For example, the following +fragment of a F<krb5.conf> file would set the default port to 4373 and the +default server to C<wallet.example.org>.  It would also set the principal +to C<wallet/example.org@EXAMPLE.NET> only if the local default realm is +EXAMPLE.NET: + +    [appdefaults] +        wallet_port = 4373 +        wallet = { +            wallet_server = wallet.example.org +            EXAMPLE.NET = { +                wallet_principal = wallet/example.org@EXAMPLE.NET +            } +        } + +The supported options are: + +=over 4 + +=item wallet_principal + +The service principal of the wallet server.  The default is to use the +C<host> principal for the wallet server.  The principal chosen must match +one of the keys in the keytab used by B<remctld> on the wallet server. +The B<-k> command-line option overrides this setting. + +=item wallet_port + +The port to connect to on the wallet server.  The default is the default +remctl port.  The B<-p> command-line option overrides this setting. + +=item wallet_server + +The wallet server to connect to.  The B<-s> command-line option overrides +this setting.  The default may be set when compiling the wallet client. +If it isn't, either B<-s> must be given or this parameter must be present +in in F<krb5.conf>. + +=item wallet_type + +The command prefix (remctl type) to use.  Normally this is an internal +implementation detail and the default (C<wallet>) should be fine.  It may +sometimes be useful to use a different prefix for testing a different +version of the wallet code on the server.  The B<-c> command-line option +overrides this setting. + +=back +  =head1 SEE ALSO  krb5.conf(5), remctl(1), remctld(8) diff --git a/configure.ac b/configure.ac index dd4b502..cef61eb 100644 --- a/configure.ac +++ b/configure.ac @@ -29,18 +29,16 @@ AC_CHECK_FUNCS([setrlimit])  AC_REPLACE_FUNCS([asprintf])  AC_ARG_WITH([wallet-server], -    AC_HELP_STRING([--with-wallet-server=HOST], [Default wallet server]), -    [if test x"$withval" != xno ; then -        AC_DEFINE_UNQUOTED([WALLET_SERVER], ["$withval"], -            [Define to the default server host name.]) -     fi]) +    [AC_HELP_STRING([--with-wallet-server=HOST], [Default wallet server])], +    [AS_IF([test x"$withval" != xno && test x"$withval" != xyes], +        [AC_DEFINE_UNQUOTED([WALLET_SERVER], ["$withval"], +            [Define to the default server host name.])])])  AC_ARG_WITH([wallet-port], -    AC_HELP_STRING([--with-wallet-port=PORT], -        [Default wallet server port]), -    [if test x"$withval" != xno ; then -        AC_DEFINE_UNQUOTED([WALLET_PORT], [$withval], -            [Define to the default server port.]) -     fi]) +    [AC_HELP_STRING([--with-wallet-port=PORT], +        [Default wallet server port])], +    [AS_IF([test x"$withval" != xno && test x"$withval" != xyes], +        [AC_DEFINE_UNQUOTED([WALLET_PORT], [$withval], +            [Define to the default server port.])])])  RRA_LIB_REMCTL  RRA_LIB_KRB5 @@ -54,10 +52,9 @@ AM_CONDITIONAL([AFS], [test x"$rra_afs" = xtrue])  AC_ARG_VAR([REMCTLD], [Path to the remctld binary])  AC_PATH_PROG([REMCTLD], [remctld], , [$PATH:/usr/sbin:/usr/local/sbin]) -if test x"$REMCTLD" != x ; then -    AC_DEFINE_UNQUOTED([PATH_REMCTLD], ["$REMCTLD"], -        [Define to the full path to remctld to run remctl tests.]) -fi +AS_IF([test x"$REMCTLD" != x], +    [AC_DEFINE_UNQUOTED([PATH_REMCTLD], ["$REMCTLD"], +        [Define to the full path to remctld to run remctl tests.])])  dnl Needed to get prototypes for functions like asprintf on Linux.  AC_DEFINE([_GNU_SOURCE], [1], [Define to 1 on Linux to get full prototypes.]) diff --git a/tests/client/basic-t.in b/tests/client/basic-t.in index 8f7632c..4a4a559 100644 --- a/tests/client/basic-t.in +++ b/tests/client/basic-t.in @@ -4,89 +4,38 @@  # Test suite for the wallet command-line client.  #  # Written by Russ Allbery <rra@stanford.edu> -# Copyright 2006, 2007 Board of Trustees, Leland Stanford Jr. University +# Copyright 2006, 2007, 2008 Board of Trustees, Leland Stanford Jr. University +#  # See LICENSE for licensing terms. -# The count starts at 1 and is updated each time ok is printed.  printcount -# takes "ok" or "not ok". -count=1 -printcount () { -    echo "$1 $count $2" -    count=`expr $count + 1` -} - -# Run a program expected to succeed, and print ok if it does and produces -# the correct output. -runsuccess () { -    w_output="$1" -    shift -    principal=`cat data/test.principal` -    output=`$wallet -k "$principal" -p 14444 -s localhost "$@" 2>&1` -    status=$? -    if [ $status = 0 ] && [ x"$output" = x"$w_output" ] ; then -        printcount "ok" -    else -        printcount "not ok" -        echo "  saw: $output" -        echo "  not: $w_output" -    fi -} - -# Run a program expected to fail and make sure it fails with the correct -# exit status and the correct failure message.  Strip the second colon and -# everything after it off the error message since it's system-specific. -runfailure () { -    w_status="$1" -    shift -    w_output="$1" -    shift -    principal=`cat data/test.principal` -    output=`$wallet -k "$principal" -p 14444 -s localhost "$@" 2>&1` -    status=$? -    output=`echo "$output" | sed 's/\(:[^:]*\):.*/\1/'` -    if [ $status = $w_status ] && [ x"$output" = x"$w_output" ] ; then -        printcount "ok" -    else -        printcount "not ok" -        echo "  saw: ($status) $output" -        echo "  not: ($w_status) $w_output" -    fi -} +# Load the test library. +. "@abs_top_srcdir@/tests/libtest.sh"  # Print the number of tests. -echo 20 +total=22 +count=1 +echo "$total"  # Find the client program. -if [ -f ../data/test.keytab ] ; then -    cd .. -else -    if [ -f tests/data/test.keytab ] ; then -        cd tests -    fi -fi -if [ ! -f data/test.keytab ] || [ -z "@REMCTLD@" ] ; then -    for n in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ; do -        echo ok $n \# skip -- no Kerberos configuration -    done +chdir_data '../client/wallet' +if [ ! -f 'data/test.keytab' ] || [ -z '@REMCTLD@' ] ; then +    skip 1 "$total" 'no Kerberos configuration'      exit 0  fi -wallet=../client/wallet -if [ ! -x "$wallet" ] ; then -    echo 'Cannot locate wallet client binary' >&2 -    exit 1 -fi +wallet='../client/wallet'  # Start the remctld daemon and wait for it to start. +principal=`cat data/test.principal`  rm -f data/pid -( @REMCTLD@ -m -p 14444 -s `cat data/test.principal` -P data/pid \ -    -f data/wallet.conf -S -F -k data/test.keytab &) +( @REMCTLD@ -m -p 14373 -s "$principal" -P data/pid -f data/wallet.conf \ +  -S -F -k data/test.keytab &)  KRB5CCNAME=data/test.cache; export KRB5CCNAME -kinit -k -t data/test.keytab `cat data/test.principal` > /dev/null 2>&1 +kinit -k -t data/test.keytab "$principal" > /dev/null 2>&1  if [ $? != 0 ] ; then -    kinit -t data/test.keytab `cat data/test.principal` > /dev/null 2>&1 +    kinit -t data/test.keytab "$principal" > /dev/null 2>&1  fi  if [ $? != 0 ] ; then -    kinit -k -K data/test.keytab `cat data/test.principal` > /dev/null 2>&1 +    kinit -k -K data/test.keytab "$principal" > /dev/null 2>&1  fi  if [ $? != 0 ] ; then      echo 'Unable to obtain Kerberos tickets' >&2 @@ -100,9 +49,10 @@ 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. +# is for.  Despite all the Stanford hard-coding, this test isn't +# Stanford-specific.  It just matches the data files shipped with the package.  krb5conf= -for p in /etc/krb5.conf /usr/local/etc/krb5.conf ; do +for p in /etc/krb5.conf /usr/local/etc/krb5.conf data/krb5.conf ; do      if [ -r "$p" ] ; then          krb5conf="$p"          sed -e '/^ *test-k5.stanford.edu =/,/}/d' \ @@ -120,12 +70,17 @@ EOF          break      fi  done +if [ -z "$krb5conf" ] ; then +    echo 'No krb5.conf found -- put one in tests/data/krb5.conf' >&2 +    exit 1 +fi  # Make sure everything's clean.  rm -f keytab keytab.bak srvtab srvtab.bak sync-kaserver -# Now, we can finally run our tests. -runsuccess "" -c fake-wallet get keytab -f keytab service/fake-test +# Now, we can finally run our tests.  First, basic operations. +runsuccess "" "$wallet" -k "$principal" -p 14373 -s localhost -c fake-wallet \ +    get keytab -f keytab service/fake-test  if cmp keytab data/fake-data >/dev/null 2>&1 ; then      printcount "ok"  else @@ -136,7 +91,8 @@ if [ -f keytab.bak ] || [ -f keytab.new ] ; then  else      printcount "ok"  fi -runsuccess "" -c fake-wallet get keytab -f keytab service/fake-test +runsuccess "" "$wallet" -k "$principal" -p 14373 -s localhost -c fake-wallet \ +    get keytab -f keytab service/fake-test  if cmp keytab data/fake-data >/dev/null 2>&1 ; then      printcount "ok"  else @@ -147,7 +103,28 @@ if [ -f keytab.new ] || [ ! -f keytab.bak ] ; then  else      printcount "ok"  fi -runsuccess "" -c fake-wallet get keytab -f keytab -S srvtab service/fake-srvtab + +# Now, append configuration to krb5.conf and test getting configuration from +# there. +cat >> krb5.conf <<EOF + +[appdefaults] +    wallet_server = localhost +    wallet = { +        wallet_port = 14373 +        wallet_type = fake-wallet +        wallet_principal = $principal +    } +EOF +runsuccess "" "$wallet" get keytab -f keytab service/fake-test +if cmp keytab data/fake-data >/dev/null 2>&1 ; then +    printcount "ok" +else +    printcount "not ok" +fi + +# Test srvtab support. +runsuccess "" "$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 @@ -165,7 +142,7 @@ if [ -f sync-kaserver ] ; then  else      printcount "not ok"  fi -runsuccess "" -c fake-wallet get keytab -f keytab -S srvtab service/fake-srvtab +runsuccess "" "$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 @@ -178,36 +155,32 @@ if [ -f sync-kaserver ] ; then  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 -    if cmp srvtab.bak data/fake-srvtab >/dev/null 2>&1 ; then -        printcount "ok" -        rm srvtab.bak -    else -        printcount "not ok" -    fi -    KRB5_CONFIG= -    rm krb5.conf +if cmp srvtab data/fake-srvtab >/dev/null 2>&1 ; then +    printcount "ok" +    rm srvtab +else +    printcount "not ok" +fi +if cmp srvtab.bak data/fake-srvtab >/dev/null 2>&1 ; then +    printcount "ok" +    rm srvtab.bak  else -    printcount "ok # skip cannot find krb5.conf" +    printcount "not ok"  fi  runsuccess "Some stuff about service/fake-test" \ -    -c fake-wallet show keytab service/fake-test +    "$wallet" show keytab service/fake-test  runfailure 1 "wallet: Unknown object type srvtab" \ -    -c fake-wallet get srvtab service/fake-test +    "$wallet" get srvtab service/fake-test  runfailure 1 "wallet: Unknown keytab service/unknown" \ -    -c fake-wallet show keytab service/unknown +    "$wallet" show keytab service/unknown  runfailure 1 "wallet: Unknown keytab service/unknown" \ -    -c fake-wallet get keytab service/unknown +    "$wallet" get keytab service/unknown  runsuccess "Expiration date of service/fake-test" \ -    -c fake-wallet expires keytab service/fake-test +    "$wallet" expires keytab service/fake-test  # Clean up. +KRB5_CONFIG= +rm data/krb5.conf  rm -f data/test.cache  if [ -f data/pid ] ; then      kill `cat data/pid` diff --git a/tests/data/README b/tests/data/README index 890c4dc..0f3c88c 100644 --- a/tests/data/README +++ b/tests/data/README @@ -8,10 +8,15 @@ to the key in the keytab on a single line ending with a newline.  The presence of these two files will enable the tests that actually do  GSS-API authentication. +If your krb5.conf file is not in /etc or /usr/local/etc, put a copy of +your krb5.conf file in this directory.  The tests need to generate a +modified copy in order to test some behavior. +  If you are building in a different directory tree than the source tree,  don't put the files in this directory.  Instead, after running configure,  you will have an empty tests/data directory in your build tree.  Put the -test.keytab and test.principal files in that directory instead. +test.keytab, test.principal, and krb5.conf (if necessary) files in that +directory instead.  Note that to successfully run much of the test suite, you will need to have  remctld installed on the system running the tests. diff --git a/tests/libtest.sh b/tests/libtest.sh new file mode 100644 index 0000000..ed46d0e --- /dev/null +++ b/tests/libtest.sh @@ -0,0 +1,82 @@ +# $Id$ +# +# Shell function library for test cases. +# +# Written by Russ Allbery <rra@stanford.edu> +# Copyright 2006, 2007, 2008 Board of Trustees, Leland Stanford Jr. University +# +# See LICENSE for licensing terms. + +# The count starts at 1 and is updated each time ok is printed.  printcount +# takes "ok" or "not ok". +count=1 +printcount () { +    echo "$1 $count $2" +    count=`expr $count + 1` +} + +# Run a program expected to succeed, and print ok if it does and produces +# the correct output.  Takes the output as the first argument, the command to +# run as the second argument, and then all subsequent arguments are arguments +# to the command. +runsuccess () { +    w_output="$1" +    shift +    output=`"$@" 2>&1` +    status=$? +    if [ $status = 0 ] && [ x"$output" = x"$w_output" ] ; then +        printcount 'ok' +    else +        printcount 'not ok' +        echo "  saw: $output" +        echo "  not: $w_output" +    fi +} + +# Run a program expected to fail and make sure it fails with the correct exit +# status and the correct failure message.  Takes the expected status, the +# expected output, and then everything else is the command and arguments. +# Strip the second colon and everything after it off the error message since +# it's system-specific. +runfailure () { +    w_status="$1" +    shift +    w_output="$1" +    shift +    output=`"$@" 2>&1` +    status=$? +    output=`echo "$output" | sed 's/\(:[^:]*\):.*/\1/'` +    if [ $status = $w_status ] && [ x"$output" = x"$w_output" ] ; then +        printcount 'ok' +    else +        printcount 'not ok' +        echo "  saw: ($status) $output" +        echo "  not: ($w_status) $w_output" +    fi +} + +# Skip tests from $1 to $2 inclusive with reason $3. +skip () { +    n="$1" +    while [ "$n" -le "$2" ] ; do +        echo ok "$n # skip $3" +        n=`expr "$n" + 1` +    done +} + +# Given a file name or relative file path, try to cd to the correct directory +# so that the relative file path is valid.  Exits with an error if that isn't +# possible. +chdir_data () { +    if [ -f "../$1" ] ; then +        cd .. +    else +        if [ -f "tests/$1" ] ; then +            cd tests +        fi +    fi +    if [ ! -f "$1" ] ; then +        echo "Cannot locate $1" >&2 +        exit 1 +    fi +} | 
