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 +} |