/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* clients/kinit/kinit.c
*
* Copyright 1990 by the Massachusetts Institute of Technology.
* All Rights Reserved.
*
* Export of this software from the United States of America may
* require a specific license from the United States Government.
* It is the responsibility of any person or organization contemplating
* export to obtain such a license before exporting.
*
* WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
* distribute this software and its documentation for any purpose and
* without fee is hereby granted, provided that the above copyright
* notice appear in all copies and that both that copyright notice and
* this permission notice appear in supporting documentation, and that
* the name of M.I.T. not be used in advertising or publicity pertaining
* to distribution of the software without specific, written prior
* permission. Furthermore if you modify this software you must label
* your software as modified software and not distribute it in such a
* fashion that it might be confused with the original M.I.T. software.
* M.I.T. makes no representations about the suitability of
* this software for any purpose. It is provided "as is" without express
* or implied warranty.
*
*
* Initialize a credentials cache.
*/
#include <k5-int.h>
#include <profile/prof_int.h>
#include <com_err.h>
#include <libintl.h>
#include <krb5.h>
#ifdef KRB5_KRB4_COMPAT
#include <kerberosIV/krb.h>
#define HAVE_KRB524
#else
#undef HAVE_KRB524
#endif
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <com_err.h>
#include <netdb.h>
#include <locale.h>
#ifdef GETOPT_LONG
#include <getopt.h>
#else
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#ifdef sun
/* SunOS4 unistd didn't declare these; okay to make unconditional? */
extern int optind;
extern char *optarg;
#endif /* sun */
#else
extern int optind;
extern char *optarg;
extern int getopt();
#endif /* HAVE_UNISTD_H */
#endif /* GETOPT_LONG */
#ifndef _WIN32
#define GET_PROGNAME(x) (strrchr((x), '/') ? strrchr((x), '/')+1 : (x))
#else
#define GET_PROGNAME(x) max(max(strrchr((x), '/'), strrchr((x), '\\')) + 1,(x))
#endif
#ifdef HAVE_PWD_H
#include <pwd.h>
static
char * get_name_from_os()
{
struct passwd *pw;
if ((pw = getpwuid((int) getuid())))
return pw->pw_name;
return 0;
}
#else /* HAVE_PWD_H */
#ifdef _WIN32
static
char * get_name_from_os()
{
static char name[1024];
DWORD name_size = sizeof(name);
if (GetUserName(name, &name_size)) {
name[sizeof(name)-1] = 0; /* Just to be extra safe */
return name;
} else {
return 0;
}
}
#else /* _WIN32 */
static
char * get_name_from_os()
{
return 0;
}
#endif /* _WIN32 */
#endif /* HAVE_PWD_H */
static char* progname_v5 = 0;
#ifdef KRB5_KRB4_COMPAT
static char* progname_v4 = 0;
static char* progname_v524 = 0;
#endif
#include <locale.h>
static int got_k5 = 0;
static int got_k4 = 0;
static int default_k5 = 1;
#if defined(KRB5_KRB4_COMPAT) && defined(KINIT_DEFAULT_BOTH)
static int default_k4 = 1;
#else
static int default_k4 = 0;
#endif
static int authed_k5 = 0;
static int authed_k4 = 0;
#define KRB4_BACKUP_DEFAULT_LIFE_SECS 24*60*60 /* 1 day */
#define ROOT_UNAME "root"
typedef enum { INIT_PW, INIT_KT, RENEW, VALIDATE } action_type;
struct k_opts
{
/* in seconds */
krb5_deltat starttime;
krb5_deltat lifetime;
krb5_deltat rlife;
int forwardable;
int proxiable;
int addresses;
int not_forwardable;
int not_proxiable;
int no_addresses;
int verbose;
char* principal_name;
char* service_name;
char* keytab_name;
char* k5_cache_name;
char* k4_cache_name;
action_type action;
int num_pa_opts;
krb5_gic_opt_pa_data *pa_opts;
};
int forwardable_flag = 0;
int renewable_flag = 0;
int proxiable_flag = 0;
int no_address_flag = 0;
profile_options_boolean config_option[] = {
{ "forwardable", &forwardable_flag, 0 },
{ "renewable", &renewable_flag, 0 },
{ "proxiable", &proxiable_flag, 0 },
{ "no_addresses", &no_address_flag, 0 },
{ NULL, NULL, 0 }
};
char *renew_timeval=NULL;
char *life_timeval=NULL;
int lifetime_specified;
int renewtime_specified;
profile_option_strings config_times[] = {
{ "max_life", &life_timeval, 0 },
{ "max_renewable_life", &renew_timeval, 0 },
{ NULL, NULL, 0 }
};
struct k5_data
{
krb5_context ctx;
krb5_ccache cc;
krb5_principal me;
char* name;
};
struct k4_data
{
krb5_deltat lifetime;
#ifdef KRB5_KRB4_COMPAT
char aname[ANAME_SZ + 1];
char inst[INST_SZ + 1];
char realm[REALM_SZ + 1];
char name[ANAME_SZ + 1 + INST_SZ + 1 + REALM_SZ + 1];
#endif
};
char *realmdef[] = { "realms", NULL, "kinit", NULL };
char *appdef[] = { "appdefaults", "kinit", NULL };
#define krb_realm (*(realmdef + 1))
#define lifetime_specified config_times[0].found
#define renewtime_specified config_times[1].found
/*
* Try no preauthentication first; then try the encrypted timestamp
*/
krb5_preauthtype * preauth = NULL;
krb5_preauthtype preauth_list[2] = { 0, -1 };
static void _kwarnd_add_warning(char *, char *, time_t);
static void _kwarnd_del_warning(char *, char *);
#ifdef GETOPT_LONG
/* if struct[2] == NULL, then long_getopt acts as if the short flag
struct[3] was specified. If struct[2] != NULL, then struct[3] is
stored in *(struct[2]), the array index which was specified is
stored in *index, and long_getopt() returns 0. */
struct option long_options[] = {
{ "noforwardable", 0, NULL, 'F' },
{ "noproxiable", 0, NULL, 'P' },
{ "addresses", 0, NULL, 'a'},
{ "forwardable", 0, NULL, 'f' },
{ "proxiable", 0, NULL, 'p' },
{ "noaddresses", 0, NULL, 'A' },
{ NULL, 0, NULL, 0 }
};
#define GETOPT(argc, argv, str) getopt_long(argc, argv, str, long_options, 0)
#else
#define GETOPT(argc, argv, str) getopt(argc, argv, str)
#endif
static void
usage(progname)
char *progname;
{
#define USAGE_BREAK "\n\t"
#ifdef GETOPT_LONG
#define USAGE_LONG_FORWARDABLE " | --forwardable | --noforwardable"
#define USAGE_LONG_PROXIABLE " | --proxiable | --noproxiable"
#define USAGE_LONG_ADDRESSES " | --addresses | --noaddresses"
#define USAGE_BREAK_LONG USAGE_BREAK
#else
#define USAGE_LONG_FORWARDABLE ""
#define USAGE_LONG_PROXIABLE ""
#define USAGE_LONG_ADDRESSES ""
#define USAGE_BREAK_LONG ""
#endif
fprintf(stderr, "%s : %s [-V] "
"[-l lifetime] [-s start_time] "
USAGE_BREAK
"[-r renewable_life] "
"[-f | -F" USAGE_LONG_FORWARDABLE "] "
USAGE_BREAK_LONG
"[-p | -P" USAGE_LONG_PROXIABLE "] "
USAGE_BREAK_LONG
"[-a | -A" USAGE_LONG_ADDRESSES "] "
USAGE_BREAK
"[-v] [-R] "
"[-k [-t keytab_file]] "
"[-c cachename] "
USAGE_BREAK
"[-S service_name]"
"[-X <attribute>[=<value>]] [principal]"
"\n\n",
gettext("Usage"), progname);
#define KRB_AVAIL_STRING(x) ((x)?gettext("available"):gettext("not available"))
#define OPTTYPE_KRB5 "5"
#define OPTTYPE_KRB4 "4"
#define OPTTYPE_EITHER "Either 4 or 5"
#ifdef HAVE_KRB524
#define OPTTYPE_BOTH "5, or both 5 and 4"
#else
#define OPTTYPE_BOTH "5"
#endif
#ifdef KRB5_KRB4_COMPAT
#define USAGE_OPT_FMT "%s%-50s%s\n"
#define ULINE(indent, col1, col2) \
fprintf(stderr, USAGE_OPT_FMT, indent, col1, col2)
#else
#define USAGE_OPT_FMT "%s%s\n"
#define ULINE(indent, col1, col2) \
fprintf(stderr, USAGE_OPT_FMT, indent, col1)
#endif
ULINE(" ", "options:", "valid with Kerberos:");
fprintf(stderr, "\t-5 Kerberos 5 (%s)\n", KRB_AVAIL_STRING(got_k5));
fprintf(stderr, "\t-4 Kerberos 4 (%s)\n", KRB_AVAIL_STRING(got_k4));
fprintf(stderr, "\t (Default behavior is to try %s%s%s%s)\n",
default_k5?"Kerberos 5":"",
(default_k5 && default_k4)?gettext(" and "):"",
default_k4?"Kerberos 4":"",
(!default_k5 && !default_k4)?gettext("neither"):"");
ULINE("\t", gettext("-V verbose"), OPTTYPE_EITHER);
ULINE("\t", gettext("-l lifetime"), OPTTYPE_EITHER);
ULINE("\t", gettext("-s start time"), OPTTYPE_KRB5);
ULINE("\t", gettext("-r renewable lifetime"), OPTTYPE_KRB5);
ULINE("\t", gettext("-f forwardable"), OPTTYPE_KRB5);
ULINE("\t", gettext("-F not forwardable"), OPTTYPE_KRB5);
ULINE("\t", gettext("-p proxiable"), OPTTYPE_KRB5);
ULINE("\t", gettext("-P not proxiable"), OPTTYPE_KRB5);
ULINE("\t", gettext("-A do not include addresses"), OPTTYPE_KRB5);
ULINE("\t", gettext("-a include addresses"), OPTTYPE_KRB5);
ULINE("\t", gettext("-v validate"), OPTTYPE_KRB5);
ULINE("\t", gettext("-R renew"), OPTTYPE_BOTH);
ULINE("\t", gettext("-k use keytab"), OPTTYPE_BOTH);
ULINE("\t", gettext("-t filename of keytab to use"), OPTTYPE_BOTH);
ULINE("\t", gettext("-c Kerberos 5 cache name"), OPTTYPE_KRB5);
/* This options is not yet available: */
/* ULINE("\t", "-C Kerberos 4 cache name", OPTTYPE_KRB4); */
ULINE("\t", gettext("-S service"), OPTTYPE_BOTH);
ULINE("\t", gettext("-X <attribute>[=<value>]"), OPTTYPE_KRB5);
exit(2);
}
static krb5_context errctx;
static void extended_com_err_fn (const char *myprog, errcode_t code,
const char *fmt, va_list args)
{
const char *emsg;
emsg = krb5_get_error_message (errctx, code);
fprintf (stderr, "%s: %s ", myprog, emsg);
krb5_free_error_message (errctx, emsg);
vfprintf (stderr, fmt, args);
fprintf (stderr, "\n");
}
static int
add_preauth_opt(struct k_opts *opts, char *av)
{
char *sep, *v;
krb5_gic_opt_pa_data *p, *x;
if (opts->num_pa_opts == 0) {
opts->pa_opts = malloc(sizeof(krb5_gic_opt_pa_data));
if (opts->pa_opts == NULL)
return ENOMEM;
} else {
size_t newsize = (opts->num_pa_opts + 1) * sizeof(krb5_gic_opt_pa_data);
x = realloc(opts->pa_opts, newsize);
if (x == NULL)
return ENOMEM;
opts->pa_opts = x;
}
p = &opts->pa_opts[opts->num_pa_opts];
sep = strchr(av, '=');
if (sep) {
*sep = '\0';
v = ++sep;
p->value = v;
} else {
p->value = "yes";
}
p->attr = av;
opts->num_pa_opts++;
return 0;
}
static char *
parse_options(argc, argv, opts, progname)
int argc;
char **argv;
struct k_opts* opts;
char *progname;
{
krb5_error_code code;
int errflg = 0;
int use_k4 = 0;
int use_k5 = 0;
int i;
while ((i = GETOPT(argc, argv, "r:fpFP54aAVl:s:c:kt:RS:vX:"))
!= -1) {
switch (i) {
case 'V':
opts->verbose = 1;
break;
case 'l':
/* Lifetime */
code = krb5_string_to_deltat(optarg, &opts->lifetime);
if (code != 0 || opts->lifetime == 0) {
fprintf(stderr, gettext("Bad lifetime value %s\n"), optarg);
errflg++;
}
break;
case 'r':
/* Renewable Time */
code = krb5_string_to_deltat(optarg, &opts->rlife);
if (code != 0 || opts->rlife == 0) {
fprintf(stderr, gettext("Bad lifetime value %s\n"), optarg);
errflg++;
}
break;
case 'f':
opts->forwardable = 1;
break;
case 'F':
opts->not_forwardable = 1;
break;
case 'p':
opts->proxiable = 1;
break;
case 'P':
opts->not_proxiable = 1;
break;
case 'a':
/* Note: This is supported only with GETOPT_LONG */
opts->addresses = 1;
break;
case 'A':
opts->no_addresses = 1;
break;
case 's':
code = krb5_string_to_deltat(optarg, &opts->starttime);
if (code != 0 || opts->starttime == 0) {
krb5_timestamp abs_starttime;
code = krb5_string_to_timestamp(optarg, &abs_starttime);
if (code != 0 || abs_starttime == 0) {
fprintf(stderr, gettext("Bad start time value %s\n"), optarg);
errflg++;
} else {
opts->starttime = abs_starttime - time(0);
}
}
break;
case 'S':
opts->service_name = optarg;
break;
case 'k':
opts->action = INIT_KT;
break;
case 't':
if (opts->keytab_name)
{
fprintf(stderr, gettext("Only one -t option allowed.\n"));
errflg++;
} else {
opts->keytab_name = optarg;
}
break;
case 'R':
opts->action = RENEW;
break;
case 'v':
opts->action = VALIDATE;
break;
case 'c':
if (opts->k5_cache_name)
{
fprintf(stderr, gettext("Only one -c option allowed\n"));
errflg++;
} else {
opts->k5_cache_name = optarg;
}
break;
case 'X':
code = add_preauth_opt(opts, optarg);
if (code)
{
com_err(progname, code, "while adding preauth option");
errflg++;
}
break;
#if 0
/*
A little more work is needed before we can enable this
option.
*/
case 'C':
if (opts->k4_cache_name)
{
fprintf(stderr, "Only one -C option allowed\n");
errflg++;
} else {
opts->k4_cache_name = optarg;
}
break;
#endif
case '4':
if (!got_k4)
{
#ifdef KRB5_KRB4_COMPAT
fprintf(stderr, "Kerberos 4 support could not be loaded\n");
#else
fprintf(stderr, gettext("This was not built with Kerberos 4 support\n"));
#endif
exit(3);
}
use_k4 = 1;
break;
case '5':
if (!got_k5)
{
fprintf(stderr, gettext("Kerberos 5 support could not be loaded\n"));
exit(3);
}
use_k5 = 1;
break;
default:
errflg++;
break;
}
}
if (opts->forwardable && opts->not_forwardable)
{
fprintf(stderr, gettext("Only one of -f and -F allowed\n"));
errflg++;
}
if (opts->proxiable && opts->not_proxiable)
{
fprintf(stderr, gettext("Only one of -p and -P allowed\n"));
errflg++;
}
if (opts->addresses && opts->no_addresses)
{
fprintf(stderr, gettext("Only one of -a and -A allowed\n"));
errflg++;
}
if (argc - optind > 1) {
fprintf(stderr, gettext("Extra arguments (starting with \"%s\").\n"),
argv[optind+1]);
errflg++;
}
/* At this point, if errorless, we know we only have one option
selection */
if (!use_k5 && !use_k4) {
use_k5 = default_k5;
use_k4 = default_k4;
}
/* Now, we encode the OPTTYPE stuff here... */
if (!use_k5 &&
(opts->starttime || opts->rlife || opts->forwardable ||
opts->proxiable || opts->addresses || opts->not_forwardable ||
opts->not_proxiable || opts->no_addresses ||
(opts->action == VALIDATE) || opts->k5_cache_name))
{
fprintf(stderr, gettext("Specified option that requires Kerberos 5\n"));
errflg++;
}
if (!use_k4 &&
opts->k4_cache_name)
{
fprintf(stderr, gettext("Specified option that require Kerberos 4\n"));
errflg++;
}
if (
#ifdef HAVE_KRB524
!use_k5
#else
use_k4
#endif
&& (opts->service_name || opts->keytab_name ||
(opts->action == INIT_KT) || (opts->action == RENEW))
)
{
fprintf(stderr, gettext("Specified option that requires Kerberos 5\n"));
errflg++;
}
if (errflg) {
usage(progname);
}
got_k5 = got_k5 && use_k5;
got_k4 = got_k4 && use_k4;
opts->principal_name = (optind == argc-1) ? argv[optind] : 0;
return opts->principal_name;
}
static int
k5_begin(opts, k5, k4)
struct k_opts* opts;
struct k5_data* k5;
struct k4_data* k4;
{
char* progname = progname_v5;
krb5_error_code code = 0;
if (!got_k5)
return 0;
code = krb5_init_context(&k5->ctx);
if (code) {
com_err(progname, code, gettext("while initializing Kerberos 5 library"));
return 0;
}
errctx = k5->ctx;
if (opts->k5_cache_name)
{
code = krb5_cc_resolve(k5->ctx, opts->k5_cache_name, &k5->cc);
if (code != 0) {
com_err(progname, code, gettext("resolving ccache %s"),
opts->k5_cache_name);
return 0;
}
}
else
{
if ((code = krb5_cc_default(k5->ctx, &k5->cc))) {
com_err(progname, code, gettext("while getting default ccache"));
return 0;
}
}
if (opts->principal_name)
{
/* Use specified name */
if ((code = krb5_parse_name(k5->ctx, opts->principal_name,
&k5->me))) {
com_err(progname, code, gettext("when parsing name %s"),
opts->principal_name);
return 0;
}
}
else
{
/* No principal name specified */
if (opts->action == INIT_KT) {
/* Use the default host/service name */
code = krb5_sname_to_principal(k5->ctx, NULL, NULL,
KRB5_NT_SRV_HST, &k5->me);
if (code) {
com_err(progname, code, gettext(
"when creating default server principal name"));
return 0;
}
} else {
/* Get default principal from cache if one exists */
code = krb5_cc_get_principal(k5->ctx, k5->cc,
&k5->me);
if (code)
{
char *name = get_name_from_os();
if (!name)
{
fprintf(stderr, gettext("Unable to identify user\n"));
return 0;
}
/* use strcmp to ensure only "root" is matched */
if (strcmp(name, ROOT_UNAME) == 0)
{
if (code = krb5_sname_to_principal(k5->ctx, NULL, ROOT_UNAME,
KRB5_NT_SRV_HST, &k5->me)) {
com_err(progname, code, gettext(
"when creating default server principal name"));
return 0;
}
} else
if ((code = krb5_parse_name(k5->ctx, name,
&k5->me)))
{
com_err(progname, code, gettext("when parsing name %s"),
name);
return 0;
}
}
}
}
code = krb5_unparse_name(k5->ctx, k5->me, &k5->name);
if (code) {
com_err(progname, code, gettext("when unparsing name"));
return 0;
}
opts->principal_name = k5->name;
#ifdef KRB5_KRB4_COMPAT
if (got_k4)
{
/* Translate to a Kerberos 4 principal */
code = krb5_524_conv_principal(k5->ctx, k5->me,
k4->aname, k4->inst, k4->realm);
if (code) {
k4->aname[0] = 0;
k4->inst[0] = 0;
k4->realm[0] = 0;
}
}
#endif
return 1;
}
static void
k5_end(k5)
struct k5_data* k5;
{
if (k5->name)
krb5_free_unparsed_name(k5->ctx, k5->name);
if (k5->me)
krb5_free_principal(k5->ctx, k5->me);
if (k5->cc)
krb5_cc_close(k5->ctx, k5->cc);
if (k5->ctx)
krb5_free_context(k5->ctx);
errctx = NULL;
memset(k5, 0, sizeof(*k5));
}
static int
k4_begin(opts, k4)
struct k_opts* opts;
struct k4_data* k4;
{
#ifdef KRB5_KRB4_COMPAT
char* progname = progname_v4;
int k_errno = 0;
#endif
if (!got_k4)
return 0;
#ifdef KRB5_KRB4_COMPAT
if (k4->aname[0])
goto skip;
if (opts->principal_name)
{
/* Use specified name */
k_errno = kname_parse(k4->aname, k4->inst, k4->realm,
opts->principal_name);
if (k_errno)
{
fprintf(stderr, "%s: %s\n", progname,
krb_get_err_text(k_errno));
return 0;
}
} else {
/* No principal name specified */
if (opts->action == INIT_KT) {
/* Use the default host/service name */
/* XXX - need to add this functionality */
fprintf(stderr, "%s: Kerberos 4 srvtab support is not "
"implemented\n", progname);
return 0;
} else {
/* Get default principal from cache if one exists */
k_errno = krb_get_tf_fullname(tkt_string(), k4->aname,
k4->inst, k4->realm);
if (k_errno)
{
char *name = get_name_from_os();
if (!name)
{
fprintf(stderr, "Unable to identify user\n");
return 0;
}
k_errno = kname_parse(k4->aname, k4->inst, k4->realm,
name);
if (k_errno)
{
fprintf(stderr, "%s: %s\n", progname,
krb_get_err_text(k_errno));
return 0;
}
}
}
}
if (!k4->realm[0])
krb_get_lrealm(k4->realm, 1);
if (k4->inst[0])
sprintf(k4->name, "%s.%s@%s", k4->aname, k4->inst, k4->realm);
else
sprintf(k4->name, "%s@%s", k4->aname, k4->realm);
opts->principal_name = k4->name;
skip:
if (k4->aname[0] && !k_isname(k4->aname))
{
fprintf(stderr, "%s: bad Kerberos 4 name format\n", progname);
return 0;
}
if (k4->inst[0] && !k_isinst(k4->inst))
{
fprintf(stderr, "%s: bad Kerberos 4 instance format\n", progname);
return 0;
}
if (k4->realm[0] && !k_isrealm(k4->realm))
{
fprintf(stderr, "%s: bad Kerberos 4 realm format\n", progname);
return 0;
}
#endif /* KRB5_KRB4_COMPAT */
return 1;
}
static void
k4_end(k4)
struct k4_data* k4;
{
memset(k4, 0, sizeof(*k4));
}
#ifdef KRB5_KRB4_COMPAT
static char stash_password[1024];
static int got_password = 0;
#endif /* KRB5_KRB4_COMPAT */
static krb5_error_code
KRB5_CALLCONV
kinit_prompter(
krb5_context ctx,
void *data,
const char *name,
const char *banner,
int num_prompts,
krb5_prompt prompts[]
)
{
int i;
krb5_prompt_type *types;
krb5_error_code rc =
krb5_prompter_posix(ctx, data, name, banner, num_prompts, prompts);
if (!rc && (types = krb5_get_prompt_types(ctx)))
for (i = 0; i < num_prompts; i++)
if ((types[i] == KRB5_PROMPT_TYPE_PASSWORD) ||
(types[i] == KRB5_PROMPT_TYPE_NEW_PASSWORD_AGAIN))
{
#ifdef KRB5_KRB4_COMPAT
strncpy(stash_password, prompts[i].reply->data,
sizeof(stash_password));
got_password = 1;
#endif
}
return rc;
}
static int
k5_kinit(opts, k5)
struct k_opts* opts;
struct k5_data* k5;
{
char* progname = progname_v5;
int notix = 1;
krb5_keytab keytab = 0;
krb5_creds my_creds;
krb5_error_code code = 0;
krb5_get_init_creds_opt *options = NULL;
int i;
krb5_timestamp now;
krb5_deltat lifetime = 0, rlife = 0, krb5_max_duration;
if (!got_k5)
return 0;
code = krb5_get_init_creds_opt_alloc(k5->ctx, &options);
if (code)
goto cleanup;
memset(&my_creds, 0, sizeof(my_creds));
/*
* Solaris Kerberos: added support for max_life and max_renewable_life
* which should be removed in the next minor release. See PSARC 2003/545
* for more info.
*
* Also, check krb5.conf for proxiable/forwardable/renewable/no_address
* parameter values.
*/
/* If either tkt life or renew life weren't set earlier take common steps to
* get the krb5.conf parameter values.
*/
if ((code = krb5_timeofday(k5->ctx, &now))) {
com_err(progname, code, gettext("while getting time of day"));
exit(1);
}
krb5_max_duration = KRB5_KDB_EXPIRATION - now - 60*60;
if (opts->lifetime == 0 || opts->rlife == 0) {
krb_realm = krb5_princ_realm(k5->ctx, k5->me)->data;
/* realm params take precedence */
profile_get_options_string(k5->ctx->profile, realmdef, config_times);
profile_get_options_string(k5->ctx->profile, appdef, config_times);
/* if the input opts doesn't have lifetime set and the krb5.conf
* parameter has been set, use that.
*/
if (opts->lifetime == 0 && life_timeval != NULL) {
code = krb5_string_to_deltat(life_timeval, &lifetime);
if (code != 0 || lifetime == 0 || lifetime > krb5_max_duration) {
fprintf(stderr, gettext("Bad max_life "
"value in Kerberos config file %s\n"),
life_timeval);
exit(1);
}
opts->lifetime = lifetime;
}
if (opts->rlife == 0 && renew_timeval != NULL) {
code = krb5_string_to_deltat(renew_timeval, &rlife);
if (code != 0 || rlife == 0 || rlife > krb5_max_duration) {
fprintf(stderr, gettext("Bad max_renewable_life "
"value in Kerberos config file %s\n"),
renew_timeval);
exit(1);
}
opts->rlife = rlife;
}
}
/*
* If lifetime is not set on the cmdline or in the krb5.conf
* file, default to max.
*/
if (opts->lifetime == 0)
opts->lifetime = krb5_max_duration;
profile_get_options_boolean(k5->ctx->profile,
realmdef, config_option);
profile_get_options_boolean(k5->ctx->profile,
appdef, config_option);
/* cmdline opts take precedence over krb5.conf file values */
if (!opts->not_proxiable && proxiable_flag) {
krb5_get_init_creds_opt_set_proxiable(options, 1);
}
if (!opts->not_forwardable && forwardable_flag) {
krb5_get_init_creds_opt_set_forwardable(options, 1);
}
if (renewable_flag) {
/*
* If this flag is set in krb5.conf, but rlife is 0, then
* set it to the max (and let the KDC sort it out).
*/
opts->rlife = opts->rlife ? opts->rlife : krb5_max_duration;
}
if (no_address_flag) {
/* cmdline opts will overwrite this below if needbe */
krb5_get_init_creds_opt_set_address_list(options, NULL);
}
/*
From this point on, we can goto cleanup because my_creds is
initialized.
*/
if (opts->lifetime)
krb5_get_init_creds_opt_set_tkt_life(options, opts->lifetime);
if (opts->rlife)
krb5_get_init_creds_opt_set_renew_life(options, opts->rlife);
if (opts->forwardable)
krb5_get_init_creds_opt_set_forwardable(options, 1);
if (opts->not_forwardable)
krb5_get_init_creds_opt_set_forwardable(options, 0);
if (opts->proxiable)
krb5_get_init_creds_opt_set_proxiable(options, 1);
if (opts->not_proxiable)
krb5_get_init_creds_opt_set_proxiable(options, 0);
if (opts->addresses)
{
krb5_address **addresses = NULL;
code = krb5_os_localaddr(k5->ctx, &addresses);
if (code != 0) {
com_err(progname, code, gettext("getting local addresses"));
goto cleanup;
}
krb5_get_init_creds_opt_set_address_list(options, addresses);
}
if (opts->no_addresses)
krb5_get_init_creds_opt_set_address_list(options, NULL);
if ((opts->action == INIT_KT) && opts->keytab_name)
{
code = krb5_kt_resolve(k5->ctx, opts->keytab_name, &keytab);
if (code != 0) {
com_err(progname, code, gettext("resolving keytab %s"),
opts->keytab_name);
goto cleanup;
}
}
for (i = 0; i < opts->num_pa_opts; i++) {
code = krb5_get_init_creds_opt_set_pa(k5->ctx, options,
opts->pa_opts[i].attr,
opts->pa_opts[i].value);
if (code != 0) {
com_err(progname, code, "while setting '%s'='%s'",
opts->pa_opts[i].attr, opts->pa_opts[i].value);
goto cleanup;
}
}
switch (opts->action) {
case INIT_PW:
code = krb5_get_init_creds_password(k5->ctx, &my_creds, k5->me,
0, kinit_prompter, 0,
opts->starttime,
opts->service_name,
options);
break;
case INIT_KT:
code = krb5_get_init_creds_keytab(k5->ctx, &my_creds, k5->me,
keytab,
opts->starttime,
opts->service_name,
options);
break;
case VALIDATE:
code = krb5_get_validated_creds(k5->ctx, &my_creds, k5->me, k5->cc,
opts->service_name);
break;
case RENEW:
code = krb5_get_renewed_creds(k5->ctx, &my_creds, k5->me, k5->cc,
opts->service_name);
break;
}
if (code) {
char *doing = 0;
switch (opts->action) {
case INIT_PW:
case INIT_KT:
doing = gettext("getting initial credentials");
break;
case VALIDATE:
doing = gettext("validating credentials");
break;
case RENEW:
doing = gettext("renewing credentials");
break;
}
/* If got code == KRB5_AP_ERR_V4_REPLY && got_k4, we should
let the user know that maybe he/she wants -4. */
if (code == KRB5KRB_AP_ERR_V4_REPLY && got_k4)
com_err(progname, code, "while %s\n"
"The KDC doesn't support v5. "
"You may want the -4 option in the future",
doing);
else if (code == KRB5KRB_AP_ERR_BAD_INTEGRITY)
fprintf(stderr, gettext("%s: Password incorrect while %s\n"), progname,
doing);
else
com_err(progname, code, gettext("while %s"), doing);
goto cleanup;
}
if (!opts->lifetime) {
/* We need to figure out what lifetime to use for Kerberos 4. */
opts->lifetime = my_creds.times.endtime - my_creds.times.authtime;
}
code = krb5_cc_initialize(k5->ctx, k5->cc, k5->me);
if (code) {
com_err(progname, code, gettext("when initializing cache %s"),
opts->k5_cache_name?opts->k5_cache_name:"");
goto cleanup;
}
code = krb5_cc_store_cred(k5->ctx, k5->cc, &my_creds);
if (code) {
com_err(progname, code, gettext("while storing credentials"));
goto cleanup;
}
if (opts->action == RENEW) {
_kwarnd_del_warning(progname, opts->principal_name);
_kwarnd_add_warning(progname, opts->principal_name, my_creds.times.endtime);
} else if ((opts->action == INIT_KT) || (opts->action == INIT_PW)) {
_kwarnd_add_warning(progname, opts->principal_name, my_creds.times.endtime);
}
notix = 0;
cleanup:
if (options)
krb5_get_init_creds_opt_free(k5->ctx, options);
if (my_creds.client == k5->me) {
my_creds.client = 0;
}
if (opts->pa_opts) {
free(opts->pa_opts);
opts->pa_opts = NULL;
opts->num_pa_opts = 0;
}
krb5_free_cred_contents(k5->ctx, &my_creds);
if (keytab)
krb5_kt_close(k5->ctx, keytab);
return notix?0:1;
}
static int
k4_kinit(opts, k4, ctx)
struct k_opts* opts;
struct k4_data* k4;
krb5_context ctx;
{
#ifdef KRB5_KRB4_COMPAT
char* progname = progname_v4;
int k_errno = 0;
#endif
if (!got_k4)
return 0;
if (opts->starttime)
return 0;
#ifdef KRB5_KRB4_COMPAT
if (!k4->lifetime)
k4->lifetime = opts->lifetime;
if (!k4->lifetime)
k4->lifetime = KRB4_BACKUP_DEFAULT_LIFE_SECS;
k4->lifetime = krb_time_to_life(0, k4->lifetime);
switch (opts->action)
{
case INIT_PW:
if (!got_password) {
unsigned int pwsize = sizeof(stash_password);
krb5_error_code code;
char prompt[1024];
sprintf(prompt, gettext("Password for %s: "), opts->principal_name);
stash_password[0] = 0;
/*
Note: krb5_read_password does not actually look at the
context, so we're ok even if we don't have a context. If
we cannot dynamically load krb5, we can substitute any
decent read password function instead of the krb5 one.
*/
code = krb5_read_password(ctx, prompt, 0, stash_password, &pwsize);
if (code || pwsize == 0)
{
fprintf(stderr, gettext("Error while reading password for '%s'\n"),
opts->principal_name);
memset(stash_password, 0, sizeof(stash_password));
return 0;
}
got_password = 1;
}
k_errno = krb_get_pw_in_tkt(k4->aname, k4->inst, k4->realm, "krbtgt",
k4->realm, k4->lifetime, stash_password);
if (k_errno) {
fprintf(stderr, "%s: %s\n", progname,
krb_get_err_text(k_errno));
if (authed_k5)
fprintf(stderr, gettext("Maybe your KDC does not support v4. "
"Try the -5 option next time.\n"));
return 0;
}
return 1;
#ifndef HAVE_KRB524
case INIT_KT:
fprintf(stderr, gettext("%s: srvtabs are not supported\n"), progname);
return 0;
case RENEW:
fprintf(stderr, gettext("%s: renewal of krb4 tickets is not supported\n"),
progname);
return 0;
#else
/* These cases are handled by the 524 code - this prevents the compiler
warnings of not using all the enumerated types.
*/
case INIT_KT:
case RENEW:
case VALIDATE:
return 0;
#endif
}
#endif
return 0;
}
static char*
getvprogname(v, progname)
char *v, *progname;
{
unsigned int len = strlen(progname) + 2 + strlen(v) + 2;
char *ret = malloc(len);
if (ret)
sprintf(ret, "%s(v%s)", progname, v);
else
ret = progname;
return ret;
}
#ifdef HAVE_KRB524
/* Convert krb5 tickets to krb4. */
static int try_convert524(k5)
struct k5_data* k5;
{
char * progname = progname_v524;
krb5_error_code code = 0;
int icode = 0;
krb5_principal kpcserver = 0;
krb5_creds *v5creds = 0;
krb5_creds increds;
CREDENTIALS v4creds;
if (!got_k4 || !got_k5)
return 0;
memset((char *) &increds, 0, sizeof(increds));
/*
From this point on, we can goto cleanup because increds is
initialized.
*/
if ((code = krb5_build_principal(k5->ctx,
&kpcserver,
krb5_princ_realm(k5->ctx, k5->me)->length,
krb5_princ_realm(k5->ctx, k5->me)->data,
"krbtgt",
krb5_princ_realm(k5->ctx, k5->me)->data,
NULL))) {
com_err(progname, code, gettext(
"while creating service principal name"));
goto cleanup;
}
increds.client = k5->me;
increds.server = kpcserver;
/* Prevent duplicate free calls. */
kpcserver = 0;
increds.times.endtime = 0;
increds.keyblock.enctype = ENCTYPE_DES_CBC_CRC;
if ((code = krb5_get_credentials(k5->ctx, 0,
k5->cc,
&increds,
&v5creds))) {
com_err(progname, code,
gettext("getting V5 credentials"));
goto cleanup;
}
if ((icode = krb524_convert_creds_kdc(k5->ctx,
v5creds,
&v4creds))) {
com_err(progname, icode,
gettext("converting to V4 credentials"));
goto cleanup;
}
/* this is stolen from the v4 kinit */
/* initialize ticket cache */
if ((icode = in_tkt(v4creds.pname, v4creds.pinst)
!= KSUCCESS)) {
com_err(progname, icode, gettext(
"trying to create the V4 ticket file"));
goto cleanup;
}
/* stash ticket, session key, etc. for future use */
if ((icode = krb_save_credentials(v4creds.service,
v4creds.instance,
v4creds.realm,
v4creds.session,
v4creds.lifetime,
v4creds.kvno,
&(v4creds.ticket_st),
v4creds.issue_date))) {
com_err(progname, icode, gettext(
"trying to save the V4 ticket"));
goto cleanup;
}
cleanup:
memset(&v4creds, 0, sizeof(v4creds));
if (v5creds)
krb5_free_creds(k5->ctx, v5creds);
increds.client = 0;
krb5_free_cred_contents(k5->ctx, &increds);
if (kpcserver)
krb5_free_principal(k5->ctx, kpcserver);
return !(code || icode);
}
#endif /* HAVE_KRB524 */
int
main(argc, argv)
int argc;
char **argv;
{
struct k_opts opts;
struct k5_data k5;
struct k4_data k4;
char *progname;
(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)
#define TEXT_DOMAIN "SYS_TEST"
#endif
(void) textdomain(TEXT_DOMAIN);
progname = GET_PROGNAME(argv[0]);
progname_v5 = getvprogname("5", progname);
#ifdef KRB5_KRB4_COMPAT
progname_v4 = getvprogname("4", progname);
progname_v524 = getvprogname("524", progname);
#endif
/* Ensure we can be driven from a pipe */
if(!isatty(fileno(stdin)))
setvbuf(stdin, 0, _IONBF, 0);
if(!isatty(fileno(stdout)))
setvbuf(stdout, 0, _IONBF, 0);
if(!isatty(fileno(stderr)))
setvbuf(stderr, 0, _IONBF, 0);
/*
This is where we would put in code to dynamically load Kerberos
libraries. Currenlty, we just get them implicitly.
*/
got_k5 = 1;
#ifdef KRB5_KRB4_COMPAT
got_k4 = 1;
#endif
memset(&opts, 0, sizeof(opts));
opts.action = INIT_PW;
memset(&k5, 0, sizeof(k5));
memset(&k4, 0, sizeof(k4));
set_com_err_hook (extended_com_err_fn);
parse_options(argc, argv, &opts, progname);
got_k5 = k5_begin(&opts, &k5, &k4);
got_k4 = k4_begin(&opts, &k4);
authed_k5 = k5_kinit(&opts, &k5);
#ifdef HAVE_KRB524
if (authed_k5)
authed_k4 = try_convert524(&k5);
#endif
if (!authed_k4)
authed_k4 = k4_kinit(&opts, &k4, k5.ctx);
#ifdef KRB5_KRB4_COMPAT
memset(stash_password, 0, sizeof(stash_password));
#endif
if (authed_k5 && opts.verbose)
fprintf(stderr, gettext("Authenticated to Kerberos v5\n"));
if (authed_k4 && opts.verbose)
fprintf(stderr, gettext("Authenticated to Kerberos v4\n"));
k5_end(&k5);
k4_end(&k4);
if ((got_k5 && !authed_k5) || (got_k4 && !authed_k4) ||
(!got_k5 && !got_k4))
exit(1);
return 0;
}
static void
_kwarnd_add_warning(char *progname, char *me, time_t endtime)
{
if (kwarn_add_warning(me, endtime) != 0)
fprintf(stderr, gettext(
"%s: no ktkt_warnd warning possible\n"), progname);
return;
}
static void
_kwarnd_del_warning(char *progname, char *me)
{
if (kwarn_del_warning(me) != 0)
fprintf(stderr, gettext(
"%s: unable to delete ktkt_warnd message for %s\n"),
progname, me);
return;
}