/*
 * 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 <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>

#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

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 */

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

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

#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, "Usage: %s [-5] [-4] [-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" USAGE_LONG_ADDRESSES "] "
	    USAGE_BREAK
	    "[-v] [-R] "
	    "[-k [-t keytab_file]] "
	    USAGE_BREAK
	    "[-c cachename] "
	    "[-S service_name] [principal]"
	    "\n\n", 
	    progname);

#define KRB_AVAIL_STRING(x) ((x)?"available":"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)?" and ":"",
	    default_k4?"Kerberos 4":"",
	    (!default_k5 && !default_k4)?"neither":"");
    ULINE("\t", "-V verbose",                   OPTTYPE_EITHER);
    ULINE("\t", "-l lifetime",                  OPTTYPE_EITHER);
    ULINE("\t", "-s start time",                OPTTYPE_KRB5);
    ULINE("\t", "-r renewable lifetime",        OPTTYPE_KRB5);
    ULINE("\t", "-f forwardable",               OPTTYPE_KRB5);
    ULINE("\t", "-F not forwardable",           OPTTYPE_KRB5);
    ULINE("\t", "-p proxiable",                 OPTTYPE_KRB5);
    ULINE("\t", "-P not proxiable",             OPTTYPE_KRB5);
    ULINE("\t", "-A do not include addresses",  OPTTYPE_KRB5);
    ULINE("\t", "-v validate",                  OPTTYPE_KRB5);
    ULINE("\t", "-R renew",                     OPTTYPE_BOTH);
    ULINE("\t", "-k use keytab",                OPTTYPE_BOTH);
    ULINE("\t", "-t filename of keytab to use", OPTTYPE_BOTH);
    ULINE("\t", "-c Kerberos 5 cache name",     OPTTYPE_KRB5);
    /* This options is not yet available: */
    /* ULINE("\t", "-C Kerberos 4 cache name",     OPTTYPE_KRB4); */
    ULINE("\t", "-S service",                   OPTTYPE_BOTH);
    exit(2);
}

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:fpFP54AVl:s:c:kt:RS:v"))
	   != -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, "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, "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, "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, "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, "Only one -c option allowed\n");
		errflg++;
	    } else {
		opts->k5_cache_name = optarg;
	    }
	    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, "This was not built with Kerberos 4 support\n");
#endif
		exit(3);
	    }
	    use_k4 = 1;
	    break;
	case '5':
	    if (!got_k5)
	    {
		fprintf(stderr, "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, "Only one of -f and -F allowed\n");
	errflg++;
    }
    if (opts->proxiable && opts->not_proxiable)
    {
	fprintf(stderr, "Only one of -p and -P allowed\n");
	errflg++;
    }
    if (opts->addresses && opts->no_addresses)
    {
	fprintf(stderr, "Only one of -a and -A allowed\n");
	errflg++;
    }

    if (argc - optind > 1) {
	fprintf(stderr, "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, "Specified option that requires Kerberos 5\n");
	errflg++;
    }
    if (!use_k4 &&
	opts->k4_cache_name)
    {
	fprintf(stderr, "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, "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, "while initializing Kerberos 5 library");
	return 0;
    }
    if (opts->k5_cache_name)
    {
	code = krb5_cc_resolve(k5->ctx, opts->k5_cache_name, &k5->cc);
	if (code != 0) {
	    com_err(progname, code, "resolving ccache %s",
		    opts->k5_cache_name);
	    return 0;
	}
    } 
    else
    {
	if ((code = krb5_cc_default(k5->ctx, &k5->cc))) {
	    com_err(progname, code, "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, "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,
		    "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, "Unable to identify user\n");
		  return 0;
		}
	      if ((code = krb5_parse_name(k5->ctx, name, 
					  &k5->me)))
		{
		  com_err(progname, code, "when parsing name %s", 
			  name);
		  return 0;
		}
	    }
	}
    }

    code = krb5_unparse_name(k5->ctx, k5->me, &k5->name);
    if (code) {
	com_err(progname, code, "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);
    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;

    if (!got_k5)
	return 0;

    krb5_get_init_creds_opt_init(&options);
    memset(&my_creds, 0, sizeof(my_creds));

    /*
      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, "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, "resolving keytab %s", 
		    opts->keytab_name);
	    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 = "getting initial credentials";
	    break;
	case VALIDATE:
	    doing = "validating credentials";
	    break;
	case RENEW:
	    doing = "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, "%s: Password incorrect while %s\n", progname,
		    doing);
	else
	    com_err(progname, code, "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, "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, "while storing credentials");
	goto cleanup;
    }

    notix = 0;

 cleanup:
    if (my_creds.client == k5->me) {
	my_creds.client = 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, "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, "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, "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, "%s: srvtabs are not supported\n", progname);
	return 0;
    case RENEW:
	fprintf(stderr, "%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,
		"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,
		"getting V5 credentials");
	goto cleanup;
    }
    if ((icode = krb524_convert_creds_kdc(k5->ctx,
					  v5creds,
					  &v4creds))) {
	com_err(progname, icode, 
		"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,
		"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,
		"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;


    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));

    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, "Authenticated to Kerberos v5\n");
    if (authed_k4 && opts.verbose)
	fprintf(stderr, "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;
}
