/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
 * Copyright (c) 1994 by the University of Southern California
 *
 * 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 copy, modify, and distribute
 *     this software and its documentation in source and binary forms is
 *     hereby granted, provided that any documentation or other materials
 *     related to such distribution or use acknowledge that the software
 *     was developed by the University of Southern California.
 *
 * DISCLAIMER OF WARRANTY.  THIS SOFTWARE IS PROVIDED "AS IS".  The
 *     University of Southern California MAKES NO REPRESENTATIONS OR
 *     WARRANTIES, EXPRESS OR IMPLIED.  By way of example, but not
 *     limitation, the University of Southern California MAKES NO
 *     REPRESENTATIONS OR WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY
 *     PARTICULAR PURPOSE. The University of Southern
 *     California shall not be held liable for any liability nor for any
 *     direct, indirect, or consequential damages with respect to any
 *     claim by the user or distributor of the ksu software.
 *
 * KSU was written by:  Ari Medvinsky, ari@isi.edu
 */

#include "ksu.h"


void plain_dump_principal(krb5_context, krb5_principal);

krb5_boolean
krb5_auth_check(krb5_context context, krb5_principal client_pname,
                char *hostname, krb5_get_init_creds_opt *options,
                char *target_user, krb5_ccache cc, int *path_passwd,
                uid_t target_uid)
{
    krb5_principal client = NULL;
    krb5_verify_init_creds_opt vfy_opts;
    krb5_creds tgt = { 0 }, tgtq = { 0 };
    krb5_error_code retval =0;
    int got_it = 0;
    krb5_boolean zero_password;
    krb5_boolean ok = FALSE;

    *path_passwd = 0;

    if ((retval= krb5_copy_principal(context,  client_pname, &client))){
        com_err(prog_name, retval, _("while copying client principal"));
        goto cleanup;
    }

    if ((retval= krb5_copy_principal(context,  client, &tgtq.client))){
        com_err(prog_name, retval, _("while copying client principal"));
        goto cleanup;
    }

    if ((retval = ksu_tgtname(context,  krb5_princ_realm(context, client),
                              krb5_princ_realm(context, client),
                              &tgtq.server))){
        com_err(prog_name, retval, _("while creating tgt for local realm"));
        goto cleanup;
    }

    if (auth_debug){ dump_principal(context, "local tgt principal name", tgtq.server ); }
    retval = krb5_cc_retrieve_cred(context, cc,
                                   KRB5_TC_MATCH_SRV_NAMEONLY | KRB5_TC_SUPPORTED_KTYPES,
                                   &tgtq, &tgt);

    if (! retval) retval = krb5_check_exp(context, tgt.times);

    if (retval){
        if ((retval != KRB5_CC_NOTFOUND) &&
            (retval != KRB5KRB_AP_ERR_TKT_EXPIRED)){
            com_err(prog_name, retval, _("while retrieving creds from cache"));
            goto cleanup;
        }
    } else{
        got_it = 1;
    }

    if (! got_it){

#ifdef GET_TGT_VIA_PASSWD
        if (krb5_seteuid(0)||krb5_seteuid(target_uid)) {
            com_err("ksu", errno, _("while switching to target uid"));
            goto cleanup;
        }


        fprintf(stderr, _("WARNING: Your password may be exposed if you enter "
                          "it here and are logged \n"));
        fprintf(stderr, _("         in remotely using an unsecure "
                          "(non-encrypted) channel. \n"));

        /*get the ticket granting ticket, via passwd(prompt for passwd)*/
        if (ksu_get_tgt_via_passwd(context, client, options, &zero_password,
                                   &tgt) == FALSE) {
            krb5_seteuid(0);

            goto cleanup;
        }
        *path_passwd = 1;
        if (krb5_seteuid(0)) {
            com_err("ksu", errno, _("while reclaiming root uid"));
            goto cleanup;
        }

#else
        plain_dump_principal (context, client);
        fprintf(stderr,
                _("does not have any appropriate tickets in the cache.\n"));
        goto cleanup;

#endif /* GET_TGT_VIA_PASSWD */

    }

    krb5_verify_init_creds_opt_init(&vfy_opts);
    krb5_verify_init_creds_opt_set_ap_req_nofail( &vfy_opts, 1);
    retval = krb5_verify_init_creds(context, &tgt, NULL, NULL, NULL,
                                    &vfy_opts);
    if (retval) {
        com_err(prog_name, retval, _("while verifying ticket for server"));
        goto cleanup;
    }

    ok = TRUE;

cleanup:
    krb5_free_principal(context, client);
    krb5_free_cred_contents(context, &tgt);
    krb5_free_cred_contents(context, &tgtq);
    return ok;
}

krb5_boolean
ksu_get_tgt_via_passwd(krb5_context context, krb5_principal client,
                       krb5_get_init_creds_opt *options,
                       krb5_boolean *zero_password, krb5_creds *creds_out)
{
    krb5_boolean ok = FALSE;
    krb5_error_code code;
    krb5_creds creds = { 0 };
    krb5_timestamp now;
    unsigned int pwsize;
    char password[255], prompt[255], *client_name = NULL;
    int result;

    *zero_password = FALSE;
    if (creds_out != NULL)
        memset(creds_out, 0, sizeof(*creds_out));

    if ((code = krb5_unparse_name(context, client, &client_name))) {
        com_err (prog_name, code, _("when unparsing name"));
        goto cleanup;
    }

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

    if ((code = krb5_timeofday(context, &now))) {
        com_err(prog_name, code, _("while getting time of day"));
        goto cleanup;
    }

    result = snprintf(prompt, sizeof(prompt), _("Kerberos password for %s: "),
                      client_name);
    if (SNPRINTF_OVERFLOW(result, sizeof(prompt))) {
        fprintf(stderr,
                _("principal name %s too long for internal buffer space\n"),
                client_name);
        goto cleanup;
    }

    pwsize = sizeof(password);

    code = krb5_read_password(context, prompt, 0, password, &pwsize);
    if (code ) {
        com_err(prog_name, code, _("while reading password for '%s'\n"),
                client_name);
        goto cleanup;
    }

    if ( pwsize == 0) {
        fprintf(stderr, _("No password given\n"));
        *zero_password = TRUE;
        goto cleanup;
    }

    code = krb5_get_init_creds_password(context, &creds, client, password,
                                        krb5_prompter_posix, NULL, 0, NULL,
                                        options);
    zap(password, sizeof(password));


    if (code) {
        if (code == KRB5KRB_AP_ERR_BAD_INTEGRITY)
            fprintf(stderr, _("%s: Password incorrect\n"), prog_name);
        else
            com_err(prog_name, code, _("while getting initial credentials"));
        goto cleanup;
    }
    if (creds_out != NULL) {
        *creds_out = creds;
        memset(&creds, 0, sizeof(creds));
    }

    ok = TRUE;

cleanup:
    krb5_free_cred_contents(context, &creds);
    free(client_name);
    return ok;
}

void
dump_principal(krb5_context context, char *str, krb5_principal p)
{
    char * stname;
    krb5_error_code retval;

    if ((retval = krb5_unparse_name(context, p, &stname))) {
        fprintf(stderr, _(" %s while unparsing name\n"),
                error_message(retval));
        return;
    }
    fprintf(stderr, " %s: %s\n", str, stname);
    free(stname);
}

void
plain_dump_principal (krb5_context context, krb5_principal p)
{
    char * stname;
    krb5_error_code retval;

    if ((retval = krb5_unparse_name(context, p, &stname))) {
        fprintf(stderr, _(" %s while unparsing name\n"),
                error_message(retval));
        return;
    }
    fprintf(stderr, "%s ", stname);
    free(stname);
}
