/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/* clients/kinit/kinit.c - Initialize a credential cache */
/*
 * Copyright 1990, 2008 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.
 */

#include "autoconf.h"
#include "k5-platform.h"        /* for asprintf */
#include <krb5.h>
#include "extern.h"
#include <locale.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <com_err.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;

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 anonymous;
    int addresses;

    int not_forwardable;
    int not_proxiable;
    int no_addresses;

    int verbose;

    char* principal_name;
    char* service_name;
    char* keytab_name;
    char* k5_in_cache_name;
    char* k5_out_cache_name;
    char *armor_ccache;

    action_type action;
    int use_client_keytab;

    int num_pa_opts;
    krb5_gic_opt_pa_data *pa_opts;

    int canonicalize;
    int enterprise;
};

struct k5_data
{
    krb5_context ctx;
    krb5_ccache in_cc, out_cc;
    krb5_principal me;
    char* name;
    krb5_boolean switch_to_cache;
};

#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' },
    { "canonicalize", 0, NULL, 'C' },
    { "enterprise", 0, NULL, 'E' },
    { 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()
{
#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_LONG_CANONICALIZE " | --canonicalize"
#define USAGE_LONG_ENTERPRISE   " | --enterprise"
#define USAGE_BREAK_LONG       USAGE_BREAK
#else
#define USAGE_LONG_FORWARDABLE  ""
#define USAGE_LONG_PROXIABLE    ""
#define USAGE_LONG_ADDRESSES    ""
#define USAGE_LONG_CANONICALIZE ""
#define USAGE_LONG_ENTERPRISE   ""
#define USAGE_BREAK_LONG        ""
#endif

    fprintf(stderr, "Usage: %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
            "-n "
            "[-a | -A" USAGE_LONG_ADDRESSES "] "
            USAGE_BREAK_LONG
            "[-C" USAGE_LONG_CANONICALIZE "] "
            USAGE_BREAK
            "[-E" USAGE_LONG_ENTERPRISE "] "
            USAGE_BREAK
            "[-v] [-R] "
            "[-k [-i|-t keytab_file]] "
            "[-c cachename] "
            USAGE_BREAK
            "[-S service_name] [-T ticket_armor_cache]"
            USAGE_BREAK
            "[-X <attribute>[=<value>]] [principal]"
            "\n\n",
            progname);

    fprintf(stderr, "    options:");
    fprintf(stderr, _("\t-V verbose\n"));
    fprintf(stderr, _("\t-l lifetime\n"));
    fprintf(stderr, _("\t-s start time\n"));
    fprintf(stderr, _("\t-r renewable lifetime\n"));
    fprintf(stderr, _("\t-f forwardable\n"));
    fprintf(stderr, _("\t-F not forwardable\n"));
    fprintf(stderr, _("\t-p proxiable\n"));
    fprintf(stderr, _("\t-P not proxiable\n"));
    fprintf(stderr, _("\t-n anonymous\n"));
    fprintf(stderr, _("\t-a include addresses\n"));
    fprintf(stderr, _("\t-A do not include addresses\n"));
    fprintf(stderr, _("\t-v validate\n"));
    fprintf(stderr, _("\t-R renew\n"));
    fprintf(stderr, _("\t-C canonicalize\n"));
    fprintf(stderr, _("\t-E client is enterprise principal name\n"));
    fprintf(stderr, _("\t-k use keytab\n"));
    fprintf(stderr, _("\t-i use default client keytab (with -k)\n"));
    fprintf(stderr, _("\t-t filename of keytab to use\n"));
    fprintf(stderr, _("\t-c Kerberos 5 cache name\n"));
    fprintf(stderr, _("\t-S service\n"));
    fprintf(stderr, _("\t-T armor credential cache\n"));
    fprintf(stderr, _("\t-X <attribute>[=<value>]\n"));
    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)
    int argc;
    char **argv;
    struct k_opts* opts;
{
    krb5_error_code code;
    int errflg = 0;
    int i;

    while ((i = GETOPT(argc, argv,
                       "r:fpFPn54aAVl:s:c:kit:T:RS:vX:CEI:")) != -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 'n':
            opts->anonymous = 1;
            break;
        case 'a':
            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) {
                /* Parse as an absolute time; intentionally undocumented
                 * but left for backwards compatibility. */
                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 'i':
            opts->use_client_keytab = 1;
            break;
        case 't':
            if (opts->keytab_name)
            {
                fprintf(stderr, _("Only one -t option allowed.\n"));
                errflg++;
            } else {
                opts->keytab_name = optarg;
            }
            break;
        case 'T':
            if (opts->armor_ccache) {
                fprintf(stderr, _("Only one armor_ccache\n"));
                errflg++;
            } else opts->armor_ccache = optarg;
            break;
        case 'R':
            opts->action = RENEW;
            break;
        case 'v':
            opts->action = VALIDATE;
            break;
        case 'c':
            if (opts->k5_out_cache_name)
            {
                fprintf(stderr, _("Only one -c option allowed\n"));
                errflg++;
            } else {
                opts->k5_out_cache_name = optarg;
            }
            break;
        case 'I':
            if (opts->k5_in_cache_name) {
                fprintf(stderr, _("Only one -I option allowed\n"));
                errflg++;
            } else {
                opts->k5_in_cache_name = optarg;
            }
            break;
        case 'X':
            code = add_preauth_opt(opts, optarg);
            if (code)
            {
                com_err(progname, code, _("while adding preauth option"));
                errflg++;
            }
            break;
        case 'C':
            opts->canonicalize = 1;
            break;
        case 'E':
            opts->enterprise = 1;
            break;
        case '4':
            fprintf(stderr, _("Kerberos 4 is no longer supported\n"));
            exit(3);
            break;
        case '5':
            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 (opts->keytab_name != NULL && opts->use_client_keytab == 1)
    {
        fprintf(stderr, _("Only one of -t and -i allowed\n"));
        errflg++;
    }
    if ((opts->keytab_name != NULL || opts->use_client_keytab == 1) &&
        opts->action != INIT_KT)
    {
        opts->action = INIT_KT;
        fprintf(stderr, _("keytab specified, forcing -k\n"));
    }

    if (argc - optind > 1) {
        fprintf(stderr, _("Extra arguments (starting with \"%s\").\n"),
                argv[optind+1]);
        errflg++;
    }

    if (errflg) {
        usage();
    }

    opts->principal_name = (optind == argc-1) ? argv[optind] : 0;
    return opts->principal_name;
}

static int
k5_begin(opts, k5)
    struct k_opts* opts;
    struct k5_data* k5;
{
    krb5_error_code code = 0;
    int success = 0;
    int flags = opts->enterprise ? KRB5_PRINCIPAL_PARSE_ENTERPRISE : 0;
    krb5_ccache defcache = NULL;
    krb5_principal defcache_princ = NULL, princ;
    const char *deftype = NULL;
    char *defrealm, *name;

    code = krb5_init_context(&k5->ctx);
    if (code) {
        com_err(progname, code, _("while initializing Kerberos 5 library"));
        return 0;
    }
    errctx = k5->ctx;

    if (opts->k5_out_cache_name) {
        code = krb5_cc_resolve(k5->ctx, opts->k5_out_cache_name, &k5->out_cc);
        if (code != 0) {
            com_err(progname, code, _("resolving ccache %s"),
                    opts->k5_out_cache_name);
            goto cleanup;
        }
        if (opts->verbose) {
            fprintf(stderr, _("Using specified cache: %s\n"),
                    opts->k5_out_cache_name);
        }
    } else {
        /* Resolve the default ccache and get its type and default principal
         * (if it is initialized). */
        code = krb5_cc_default(k5->ctx, &defcache);
        if (code) {
            com_err(progname, code, _("while getting default ccache"));
            goto cleanup;
        }
        deftype = krb5_cc_get_type(k5->ctx, defcache);
        if (krb5_cc_get_principal(k5->ctx, defcache, &defcache_princ) != 0)
            defcache_princ = NULL;
    }

    /* Choose a client principal name. */
    if (opts->principal_name != NULL) {
        /* Use the specified principal name. */
        code = krb5_parse_name_flags(k5->ctx, opts->principal_name, flags,
                                     &k5->me);
        if (code) {
            com_err(progname, code, _("when parsing name %s"),
                    opts->principal_name);
            goto cleanup;
        }
    } else if (opts->anonymous) {
        /* Use the anonymous principal for the local realm. */
        code = krb5_get_default_realm(k5->ctx, &defrealm);
        if (code) {
            com_err(progname, code, _("while getting default realm"));
            goto cleanup;
        }
        code = krb5_build_principal_ext(k5->ctx, &k5->me,
                                        strlen(defrealm), defrealm,
                                        strlen(KRB5_WELLKNOWN_NAMESTR),
                                        KRB5_WELLKNOWN_NAMESTR,
                                        strlen(KRB5_ANONYMOUS_PRINCSTR),
                                        KRB5_ANONYMOUS_PRINCSTR,
                                        0);
        krb5_free_default_realm(k5->ctx, defrealm);
        if (code) {
            com_err(progname, code, _("while building principal"));
            goto cleanup;
        }
    } else 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"));
            goto cleanup;
        }
        if (k5->me->realm.data[0] == 0) {
            code = krb5_unparse_name(k5->ctx, k5->me, &k5->name);
            if (code == 0) {
                com_err(progname, KRB5_ERR_HOST_REALM_UNKNOWN,
                        _("(principal %s)"), k5->name);
            } else {
                com_err(progname, KRB5_ERR_HOST_REALM_UNKNOWN,
                        _("for local services"));
            }
            goto cleanup;
        }
    } else if (k5->out_cc != NULL) {
        /* If the output ccache is initialized, use its principal. */
        if (krb5_cc_get_principal(k5->ctx, k5->out_cc, &princ) == 0)
            k5->me = princ;
    } else if (defcache_princ != NULL) {
        /* Use the default cache's principal, and use the default cache as the
         * output cache. */
        k5->out_cc = defcache;
        defcache = NULL;
        k5->me = defcache_princ;
        defcache_princ = NULL;
    }

    /* If we still haven't chosen, use the local username. */
    if (k5->me == NULL) {
        name = get_name_from_os();
        if (name == NULL) {
            fprintf(stderr, _("Unable to identify user\n"));
            goto cleanup;
        }
        code = krb5_parse_name_flags(k5->ctx, name, flags, &k5->me);
        if (code) {
            com_err(progname, code, _("when parsing name %s"),
                    name);
            goto cleanup;
        }
    }

    if (k5->out_cc == NULL && krb5_cc_support_switch(k5->ctx, deftype)) {
        /* Use an existing cache for the client principal if we can. */
        code = krb5_cc_cache_match(k5->ctx, k5->me, &k5->out_cc);
        if (code != 0 && code != KRB5_CC_NOTFOUND) {
            com_err(progname, code, _("while searching for ccache for %s"),
                    opts->principal_name);
            goto cleanup;
        }
        if (code == 0) {
            if (opts->verbose) {
                fprintf(stderr, _("Using existing cache: %s\n"),
                        krb5_cc_get_name(k5->ctx, k5->out_cc));
            }
            k5->switch_to_cache = 1;
        } else if (defcache_princ != NULL) {
            /* Create a new cache to avoid overwriting the initialized default
             * cache. */
            code = krb5_cc_new_unique(k5->ctx, deftype, NULL, &k5->out_cc);
            if (code) {
                com_err(progname, code, _("while generating new ccache"));
                goto cleanup;
            }
            if (opts->verbose) {
                fprintf(stderr, _("Using new cache: %s\n"),
                        krb5_cc_get_name(k5->ctx, k5->out_cc));
            }
            k5->switch_to_cache = 1;
        }
    }

    /* Use the default cache if we haven't picked one yet. */
    if (k5->out_cc == NULL) {
        k5->out_cc = defcache;
        defcache = NULL;
        if (opts->verbose) {
            fprintf(stderr, _("Using default cache: %s\n"),
                    krb5_cc_get_name(k5->ctx, k5->out_cc));
        }
    }

    if (opts->k5_in_cache_name) {
        code = krb5_cc_resolve(k5->ctx, opts->k5_in_cache_name, &k5->in_cc);
        if (code != 0) {
            com_err(progname, code, _("resolving ccache %s"),
                    opts->k5_in_cache_name);
            goto cleanup;
        }
        if (opts->verbose) {
            fprintf(stderr, _("Using specified input cache: %s\n"),
                    opts->k5_in_cache_name);
        }
    }


    code = krb5_unparse_name(k5->ctx, k5->me, &k5->name);
    if (code) {
        com_err(progname, code, _("when unparsing name"));
        goto cleanup;
    }
    if (opts->verbose)
        fprintf(stderr, _("Using principal: %s\n"), k5->name);

    opts->principal_name = k5->name;

    success = 1;

cleanup:
    if (defcache != NULL)
        krb5_cc_close(k5->ctx, defcache);
    krb5_free_principal(k5->ctx, defcache_princ);
    return success;
}

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->in_cc)
        krb5_cc_close(k5->ctx, k5->in_cc);
    if (k5->out_cc)
        krb5_cc_close(k5->ctx, k5->out_cc);
    if (k5->ctx)
        krb5_free_context(k5->ctx);
    errctx = NULL;
    memset(k5, 0, sizeof(*k5));
}

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[]
)
{
    krb5_error_code rc =
        krb5_prompter_posix(ctx, data, name, banner, num_prompts, prompts);
    return rc;
}

static int
k5_kinit(opts, k5)
    struct k_opts* opts;
    struct k5_data* k5;
{
    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;

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

    code = krb5_get_init_creds_opt_alloc(k5->ctx, &options);
    if (code)
        goto cleanup;

    /*
      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->canonicalize)
        krb5_get_init_creds_opt_set_canonicalize(options, 1);
    if (opts->anonymous)
        krb5_get_init_creds_opt_set_anonymous(options, 1);
    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->armor_ccache)
        krb5_get_init_creds_opt_set_fast_ccache_name(k5->ctx, options, opts->armor_ccache);


    if ((opts->action == INIT_KT) && opts->keytab_name)
    {
#ifndef _WIN32
        if (strncmp(opts->keytab_name, "KDB:", 4) == 0) {
            code = kinit_kdb_init(&k5->ctx,
                                  krb5_princ_realm(k5->ctx, k5->me)->data);
            if (code != 0) {
                com_err(progname, code,
                        _("while setting up KDB keytab for realm %s"),
                        krb5_princ_realm(k5->ctx, k5->me)->data);
                goto cleanup;
            }
        }
#endif

        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;
        }
        if (opts->verbose)
            fprintf(stderr, _("Using keytab: %s\n"), opts->keytab_name);
    } else if (opts->action == INIT_KT && opts->use_client_keytab) {
        code = krb5_kt_client_default(k5->ctx, &keytab);
        if (code != 0) {
            com_err(progname, code, _("resolving default client keytab"));
            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;
        }
        if (opts->verbose) {
            fprintf(stderr, _("PA Option %s = %s\n"), opts->pa_opts[i].attr,
                    opts->pa_opts[i].value);
        }
    }
    if (k5->in_cc) {
        code = krb5_get_init_creds_opt_set_in_ccache(k5->ctx, options,
                                                     k5->in_cc);
        if (code)
            goto cleanup;
    }
    code = krb5_get_init_creds_opt_set_out_ccache(k5->ctx, options,
                                                  k5->out_cc);
    if (code)
        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->out_cc,
                                        opts->service_name);
        break;
    case RENEW:
        code = krb5_get_renewed_creds(k5->ctx, &my_creds, k5->me, k5->out_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 (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->action != INIT_PW) && (opts->action != INIT_KT)) {
        code = krb5_cc_initialize(k5->ctx, k5->out_cc, opts->canonicalize ?
                                  my_creds.client : k5->me);
        if (code) {
            com_err(progname, code, _("when initializing cache %s"),
                    opts->k5_out_cache_name?opts->k5_out_cache_name:"");
            goto cleanup;
        }
        if (opts->verbose)
            fprintf(stderr, _("Initialized cache\n"));

        code = krb5_cc_store_cred(k5->ctx, k5->out_cc, &my_creds);
        if (code) {
            com_err(progname, code, _("while storing credentials"));
            goto cleanup;
        }
        if (opts->verbose)
            fprintf(stderr, _("Stored credentials\n"));
    }
    notix = 0;

    if (k5->switch_to_cache) {
        code = krb5_cc_switch(k5->ctx, k5->out_cc);
        if (code) {
            com_err(progname, code, _("while switching to new ccache"));
            goto cleanup;
        }
    }

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

int
main(argc, argv)
    int argc;
    char **argv;
{
    struct k_opts opts;
    struct k5_data k5;
    int authed_k5 = 0;

    setlocale(LC_ALL, "");
    progname = GET_PROGNAME(argv[0]);

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

    memset(&opts, 0, sizeof(opts));
    opts.action = INIT_PW;

    memset(&k5, 0, sizeof(k5));

    set_com_err_hook (extended_com_err_fn);

    parse_options(argc, argv, &opts);

    if (k5_begin(&opts, &k5))
        authed_k5 = k5_kinit(&opts, &k5);

    if (authed_k5 && opts.verbose)
        fprintf(stderr, _("Authenticated to Kerberos v5\n"));

    k5_end(&k5);

    if (!authed_k5)
        exit(1);
    return 0;
}
