From b10beb347238b153af8aa544fb276485b34e970e Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sat, 5 Jan 2008 00:01:54 +0000 Subject: 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. --- client/error.c | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ client/internal.h | 30 +++++++++----- client/keytab.c | 14 +++---- client/srvtab.c | 87 +++++++-------------------------------- client/wallet.c | 117 +++++++++++++++++++++++++++++++++++++++++++--------- client/wallet.pod | 67 ++++++++++++++++++++++++++++-- 6 files changed, 323 insertions(+), 113 deletions(-) create mode 100644 client/error.c (limited to 'client') 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 +** Copyright 2006, 2007, 2008 +** Board of Trustees, Leland Stanford Jr. University +** +** See LICENSE for licensing terms. +*/ + +#include + +#include +#include +#include + +#include +#if !defined(HAVE_KRB5_GET_ERROR_MESSAGE) && !defined(HAVE_KRB5_GET_ERR_TEXT) +# if defined(HAVE_IBM_SVC_KRB5_SVC_H) +# include +# elif defined(HAVE_ET_COM_ERR_H) +# include +# else +# include +# endif +#endif + +#include +#include + +/* 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 -** 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 #include + #include /* 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 -** 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 -** 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 -** 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 #include +#include #include #include #include +/* 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] [ ...]\n\ @@ -44,6 +65,58 @@ 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. @@ -51,12 +124,11 @@ usage(int status) 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) 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; see L below. =item B<-f> I @@ -76,6 +77,7 @@ will be destroyed. The service principal of the wallet server. The default is to use the C principal for the wallet server. The principal chosen must match one of the keys in the keytab used by B on the wallet server. +This option can also be set in F; see L below. =item B<-h> @@ -85,7 +87,8 @@ commands are ignored. =item B<-p> I 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; see +L below. =item B<-S> I @@ -100,8 +103,9 @@ L. =item B<-s> I -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. See L below. =item B<-v> @@ -352,6 +356,61 @@ correctly. =back +=head1 CONFIGURATION + +B can optionally be configured in the system F. It +will read the default F file for the Kerberos libraries with +which it was compiled. To set an option, put the option in the +[appdefaults] section. B will look for options either at the top +level of the [appdefaults] section or in a subsection named C, +inside or outside of a section for the realm. For example, the following +fragment of a F file would set the default port to 4373 and the +default server to C. It would also set the principal +to C 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 principal for the wallet server. The principal chosen must match +one of the keys in the keytab used by B 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. + +=item wallet_type + +The command prefix (remctl type) to use. Normally this is an internal +implementation detail and the default (C) 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) -- cgit v1.2.3