root/usr/src/lib/gss_mechs/mech_krb5/krb5/krb/get_creds.c
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */


/*
 * lib/krb5/krb/get_creds.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.
 *
 *
 * krb5_get_credentials()
 */



/*
 Attempts to use the credentials cache or TGS exchange to get an additional
 ticket for the
 client identified by in_creds->client, the server identified by
 in_creds->server, with options options, expiration date specified in
 in_creds->times.endtime (0 means as long as possible), session key type
 specified in in_creds->keyblock.enctype (if non-zero)

 Any returned ticket and intermediate ticket-granting tickets are
 stored in ccache.

 returns errors from encryption routines, system errors
 */

#include "k5-int.h"

/*ARGSUSED*/
static krb5_error_code
krb5_get_credentials_core(krb5_context context, krb5_flags options,
                          krb5_creds *in_creds, krb5_creds *mcreds,
                          krb5_flags *fields)
{
    /* Solaris Kerberos */
    krb5_error_code ret = 0;

    if (!in_creds || !in_creds->server || !in_creds->client)
        return EINVAL;

    memset((char *)mcreds, 0, sizeof(krb5_creds));
    mcreds->magic = KV5M_CREDS;
    /*
     * Solaris Kerberos:
     * Set endtime appropriately to make sure we do not rope in
     * expired creds. If endtime is set to 0 (which it almost always
     * is, courtesy memset/calloc) the krb5_cc_retrieve_cred() call in
     * krb5_get_credentials() with KRB5_TC_MATCH_TIMES will
     * succeed and return the expired cred.
     *
     * Hence, endtime below is set to "now" if in_creds->times.endtime
     * is 0, so that krb5_cc_retrieve_cred fails and we get fresh creds,
     * if necessary. But, if in_creds has a non-zero endtime, we honor it.
     */
    if (in_creds->times.endtime != 0)
        mcreds->times.endtime = in_creds->times.endtime;
    else
        if ((ret = krb5_timeofday(context, &mcreds->times.endtime)) != 0)
                return (ret);

    ret = krb5_copy_keyblock_contents(context, &in_creds->keyblock,
                &mcreds->keyblock);
    if (ret)
        return (ret);

    mcreds->authdata = in_creds->authdata;
    mcreds->server = in_creds->server;
    mcreds->client = in_creds->client;

    *fields = KRB5_TC_MATCH_TIMES /*XXX |KRB5_TC_MATCH_SKEY_TYPE */
        | KRB5_TC_MATCH_AUTHDATA
        | KRB5_TC_SUPPORTED_KTYPES;
    if (mcreds->keyblock.enctype) {
        krb5_enctype *ktypes;
        int i;

        *fields |= KRB5_TC_MATCH_KTYPE;
        ret = krb5_get_tgs_ktypes (context, mcreds->server, &ktypes);
        for (i = 0; ktypes[i]; i++)
            if (ktypes[i] == mcreds->keyblock.enctype)
                break;
        if (ktypes[i] == 0)
            ret = KRB5_CC_NOT_KTYPE;
        free (ktypes);
        if (ret) {
            krb5_free_keyblock_contents(context, &mcreds->keyblock);
            return ret;
        }
    }
    if (options & KRB5_GC_USER_USER) {
        /* also match on identical 2nd tkt and tkt encrypted in a
           session key */
        *fields |= KRB5_TC_MATCH_2ND_TKT|KRB5_TC_MATCH_IS_SKEY;
        mcreds->is_skey = TRUE;
        mcreds->second_ticket = in_creds->second_ticket;
        if (!in_creds->second_ticket.length) {
            krb5_free_keyblock_contents(context, &mcreds->keyblock);
            return KRB5_NO_2ND_TKT;
        }
    }

    return 0;
}

krb5_error_code KRB5_CALLCONV
krb5_get_credentials(krb5_context context, krb5_flags options,
                     krb5_ccache ccache, krb5_creds *in_creds,
                     krb5_creds **out_creds)
{
    krb5_error_code retval;
    krb5_creds mcreds;
    krb5_creds *ncreds;
    krb5_creds **tgts;
    krb5_flags fields;
    int not_ktype;

    retval = krb5_get_credentials_core(context, options,
                                       in_creds,
                                       &mcreds, &fields);

    if (retval) return retval;

    if ((ncreds = (krb5_creds *)malloc(sizeof(krb5_creds))) == NULL) {
        krb5_free_keyblock_contents(context, &mcreds.keyblock);
        return ENOMEM;
    }

    memset((char *)ncreds, 0, sizeof(krb5_creds));
    ncreds->magic = KV5M_CREDS;

    /* The caller is now responsible for cleaning up in_creds */
    /* Solaris Kerberos */
    if ((retval = krb5_cc_retrieve_cred(context, ccache, fields, &mcreds,
                                        ncreds)) !=0) {
        krb5_xfree(ncreds);
        ncreds = in_creds;
    } else {
        *out_creds = ncreds;
    }

    if ((retval != KRB5_CC_NOTFOUND && retval != KRB5_CC_NOT_KTYPE)
        || options & KRB5_GC_CACHED) {
        krb5_free_keyblock_contents(context, &mcreds.keyblock);
        return retval;
    }

    if (retval == KRB5_CC_NOT_KTYPE)
        not_ktype = 1;
    else
        not_ktype = 0;

    retval = krb5_get_cred_from_kdc(context, ccache, ncreds, out_creds, &tgts);
    if (tgts) {
        register int i = 0;
        krb5_error_code rv2;
        while (tgts[i]) {
            /* Solaris Kerberos */
            if ((rv2 = krb5_cc_store_cred(context, ccache, tgts[i])) != 0) {
                retval = rv2;
                break;
            }
            i++;
        }
        krb5_free_tgt_creds(context, tgts);
    }
    /*
     * Translate KRB5_CC_NOTFOUND if we previously got
     * KRB5_CC_NOT_KTYPE from krb5_cc_retrieve_cred(), in order to
     * handle the case where there is no TGT in the ccache and the
     * input enctype didn't match.  This handling is necessary because
     * some callers, such as GSSAPI, iterate through enctypes and
     * KRB5_CC_NOTFOUND passed through from the
     * krb5_get_cred_from_kdc() is semantically incorrect, since the
     * actual failure was the non-existence of a ticket of the correct
     * enctype rather than the missing TGT.
     */
    if ((retval == KRB5_CC_NOTFOUND || retval == KRB5_CC_NOT_KTYPE)
        && not_ktype)
        retval = KRB5_CC_NOT_KTYPE;

    if (!retval) {
        /* the purpose of the krb5_get_credentials call is to
         * obtain a set of credentials for the caller.  the
         * krb5_cc_store_cred() call is to optimize performance
         * for future calls.  Ignore any errors, since the credentials
         * are still valid even if we fail to store them in the cache.
         */
        /* Solaris Kerberos */
        retval = krb5_cc_store_cred(context, ccache, *out_creds);
    }

    krb5_free_keyblock_contents(context, &mcreds.keyblock);
    return retval;
}

#define INT_GC_VALIDATE 1
#define INT_GC_RENEW 2

/*ARGSUSED*/
static krb5_error_code
krb5_get_credentials_val_renew_core(krb5_context context, krb5_flags options,
                                    krb5_ccache ccache, krb5_creds *in_creds,
                                    krb5_creds **out_creds, int which)
{
    krb5_error_code retval;
    krb5_principal tmp;
    krb5_creds **tgts = 0;

    switch(which) {
    case INT_GC_VALIDATE:
            retval = krb5_get_cred_from_kdc_validate(context, ccache,
                                             in_creds, out_creds, &tgts);
            break;
    case INT_GC_RENEW:
            retval = krb5_get_cred_from_kdc_renew(context, ccache,
                                             in_creds, out_creds, &tgts);
            break;
    default:
            /* Should never happen */
            retval = 255;
            break;
    }
    if (retval) return retval;
    if (tgts) krb5_free_tgt_creds(context, tgts);

    retval = krb5_cc_get_principal(context, ccache, &tmp);
    if (retval) return retval;

    retval = krb5_cc_initialize(context, ccache, tmp);
    /* Solaris Kerberos */
    if (retval) {
        krb5_free_principal(context, tmp);
        return retval;
    }

    retval = krb5_cc_store_cred(context, ccache, *out_creds);
    krb5_free_principal(context, tmp);
    return retval;
}

krb5_error_code KRB5_CALLCONV
krb5_get_credentials_validate(krb5_context context, krb5_flags options,
                              krb5_ccache ccache, krb5_creds *in_creds,
                              krb5_creds **out_creds)
{
    return(krb5_get_credentials_val_renew_core(context, options, ccache,
                                               in_creds, out_creds,
                                               INT_GC_VALIDATE));
}

krb5_error_code KRB5_CALLCONV
krb5_get_credentials_renew(krb5_context context, krb5_flags options,
                           krb5_ccache ccache, krb5_creds *in_creds,
                           krb5_creds **out_creds)
{

    return(krb5_get_credentials_val_renew_core(context, options, ccache,
                                               in_creds, out_creds,
                                               INT_GC_RENEW));
}

static krb5_error_code
krb5_validate_or_renew_creds(krb5_context context, krb5_creds *creds,
                             krb5_principal client, krb5_ccache ccache,
                             char *in_tkt_service, int validate)
{
    krb5_error_code ret;
    krb5_creds in_creds; /* only client and server need to be filled in */
    krb5_creds *out_creds = 0; /* for check before dereferencing below */
    krb5_creds **tgts;

    memset((char *)&in_creds, 0, sizeof(krb5_creds));

    in_creds.server = NULL;
    tgts = NULL;

    in_creds.client = client;

    if (in_tkt_service) {
        /* this is ugly, because so are the data structures involved.  I'm
           in the library, so I'm going to manipulate the data structures
           directly, otherwise, it will be worse. */

        if ((ret = krb5_parse_name(context, in_tkt_service, &in_creds.server)))
            goto cleanup;

        /* stuff the client realm into the server principal.
           realloc if necessary */
        if (in_creds.server->realm.length < in_creds.client->realm.length)
            if ((in_creds.server->realm.data =
                 (char *) realloc(in_creds.server->realm.data,
                                  in_creds.client->realm.length)) == NULL) {
                ret = ENOMEM;
                goto cleanup;
            }

        in_creds.server->realm.length = in_creds.client->realm.length;
        memcpy(in_creds.server->realm.data, in_creds.client->realm.data,
               in_creds.client->realm.length);
    } else {
        if ((ret = krb5_build_principal_ext(context, &in_creds.server,
                                           in_creds.client->realm.length,
                                           in_creds.client->realm.data,
                                           KRB5_TGS_NAME_SIZE,
                                           KRB5_TGS_NAME,
                                           in_creds.client->realm.length,
                                           in_creds.client->realm.data,
                                            0)))
            goto cleanup;
    }

    if (validate)
        ret = krb5_get_cred_from_kdc_validate(context, ccache,
                                              &in_creds, &out_creds, &tgts);
    else
        ret = krb5_get_cred_from_kdc_renew(context, ccache,
                                           &in_creds, &out_creds, &tgts);

    /* ick.  copy the struct contents, free the container */
    if (out_creds) {
        *creds = *out_creds;
        krb5_xfree(out_creds);
    }

cleanup:

    if (in_creds.server)
        krb5_free_principal(context, in_creds.server);
    if (tgts)
        krb5_free_tgt_creds(context, tgts);

    return(ret);
}

krb5_error_code KRB5_CALLCONV
krb5_get_validated_creds(krb5_context context, krb5_creds *creds, krb5_principal client, krb5_ccache ccache, char *in_tkt_service)
{
    return(krb5_validate_or_renew_creds(context, creds, client, ccache,
                                        in_tkt_service, 1));
}

krb5_error_code KRB5_CALLCONV
krb5_get_renewed_creds(krb5_context context, krb5_creds *creds, krb5_principal client, krb5_ccache ccache, char *in_tkt_service)
{
    return(krb5_validate_or_renew_creds(context, creds, client, ccache,
                                        in_tkt_service, 0));
}