2N/A/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2N/A/*
2N/A * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A/*
2N/A * lib/krb5/krb/get_in_tkt.c
2N/A *
2N/A * Copyright 1990,1991, 2003, 2008 by the Massachusetts Institute of Technology.
2N/A * All Rights Reserved.
2N/A *
2N/A * Export of this software from the United States of America may
2N/A * require a specific license from the United States Government.
2N/A * It is the responsibility of any person or organization contemplating
2N/A * export to obtain such a license before exporting.
2N/A *
2N/A * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
2N/A * distribute this software and its documentation for any purpose and
2N/A * without fee is hereby granted, provided that the above copyright
2N/A * notice appear in all copies and that both that copyright notice and
2N/A * this permission notice appear in supporting documentation, and that
2N/A * the name of M.I.T. not be used in advertising or publicity pertaining
2N/A * to distribution of the software without specific, written prior
2N/A * permission. Furthermore if you modify this software you must label
2N/A * your software as modified software and not distribute it in such a
2N/A * fashion that it might be confused with the original M.I.T. software.
2N/A * M.I.T. makes no representations about the suitability of
2N/A * this software for any purpose. It is provided "as is" without express
2N/A * or implied warranty.
2N/A *
2N/A *
2N/A * krb5_get_in_tkt()
2N/A */
2N/A
2N/A#include <string.h>
2N/A
2N/A#include "k5-int.h"
2N/A#include "int-proto.h"
2N/A#include "os-proto.h"
2N/A#include "fast.h"
2N/A#include "init_creds_ctx.h"
2N/A/* Solaris Kerberos begin */
2N/A#include <ctype.h>
2N/A#include <locale.h>
2N/A#include "kerberos_dtrace.h"
2N/A/* Solaris Kerberos end */
2N/A
2N/A#if APPLE_PKINIT
2N/A#define IN_TKT_DEBUG 0
2N/A#if IN_TKT_DEBUG
2N/A#define inTktDebug(args...) printf(args)
2N/A#else
2N/A#define inTktDebug(args...)
2N/A#endif
2N/A#endif /* APPLE_PKINIT */
2N/A
2N/A/*
2N/A All-purpose initial ticket routine, usually called via
2N/A krb5_get_in_tkt_with_password or krb5_get_in_tkt_with_skey.
2N/A
2N/A Attempts to get an initial ticket for creds->client to use server
2N/A creds->server, (realm is taken from creds->client), with options
2N/A options, and using creds->times.starttime, creds->times.endtime,
2N/A creds->times.renew_till as from, till, and rtime.
2N/A creds->times.renew_till is ignored unless the RENEWABLE option is requested.
2N/A
2N/A key_proc is called to fill in the key to be used for decryption.
2N/A keyseed is passed on to key_proc.
2N/A
2N/A decrypt_proc is called to perform the decryption of the response (the
2N/A encrypted part is in dec_rep->enc_part; the decrypted part should be
2N/A allocated and filled into dec_rep->enc_part2
2N/A arg is passed on to decrypt_proc.
2N/A
2N/A If addrs is non-NULL, it is used for the addresses requested. If it is
2N/A null, the system standard addresses are used.
2N/A
2N/A A succesful call will place the ticket in the credentials cache ccache
2N/A and fill in creds with the ticket information used/returned..
2N/A
2N/A returns system errors, encryption errors
2N/A
2N/A*/
2N/A
2N/A/* Solaris Kerberos */
2N/A#define max(a, b) ((a) > (b) ? (a) : (b))
2N/A
2N/A/* some typedef's for the function args to make things look a bit cleaner */
2N/A
2N/Atypedef krb5_error_code (*git_key_proc) (krb5_context,
2N/A krb5_enctype,
2N/A krb5_data *,
2N/A krb5_const_pointer,
2N/A krb5_keyblock **);
2N/A
2N/Atypedef krb5_error_code (*git_decrypt_proc) (krb5_context,
2N/A const krb5_keyblock *,
2N/A krb5_const_pointer,
2N/A krb5_kdc_rep * );
2N/A
2N/Astatic krb5_error_code make_preauth_list (krb5_context,
2N/A krb5_preauthtype *,
2N/A int, krb5_pa_data ***);
2N/Astatic krb5_error_code sort_krb5_padata_sequence(krb5_context context,
2N/A krb5_data *realm,
2N/A krb5_pa_data **padata);
2N/A
2N/A/*
2N/A * This function performs 32 bit bounded addition so we can generate
2N/A * lifetimes without overflowing krb5_int32
2N/A */
2N/Astatic krb5_int32
2N/Akrb5int_addint32 (krb5_int32 x, krb5_int32 y)
2N/A{
2N/A if ((x > 0) && (y > (KRB5_INT32_MAX - x))) {
2N/A /* sum will be be greater than KRB5_INT32_MAX */
2N/A return KRB5_INT32_MAX;
2N/A } else if ((x < 0) && (y < (KRB5_INT32_MIN - x))) {
2N/A /* sum will be less than KRB5_INT32_MIN */
2N/A return KRB5_INT32_MIN;
2N/A }
2N/A
2N/A return x + y;
2N/A}
2N/A
2N/A#if APPLE_PKINIT
2N/A/*
2N/A * Common code to generate krb5_kdc_req.nonce. Like the original MIT code this
2N/A * just uses krb5_timeofday(); it should use a PRNG. Even more unfortunately this
2N/A * value is used interchangeably with an explicit now_time throughout this module...
2N/A */
2N/Astatic krb5_error_code
2N/Agen_nonce(krb5_context context,
2N/A krb5_int32 *nonce)
2N/A{
2N/A krb5_int32 time_now;
2N/A krb5_error_code retval = krb5_timeofday(context, &time_now);
2N/A if(retval) {
2N/A return retval;
2N/A }
2N/A *nonce = time_now;
2N/A return 0;
2N/A}
2N/A#endif /* APPLE_PKINIT */
2N/A
2N/A/*
2N/A * This function sends a request to the KDC, and gets back a response;
2N/A * the response is parsed into ret_err_reply or ret_as_reply if the
2N/A * reponse is a KRB_ERROR or a KRB_AS_REP packet. If it is some other
2N/A * unexpected response, an error is returned.
2N/A */
2N/A
2N/A/*
2N/A * Solaris Kerberos:
2N/A * Modified to return the raw reply ("rep") so it can be used for the Kerberos
2N/A * DTrace probes. "rep->data" must be freed by the caller.
2N/A */
2N/Astatic krb5_error_code
2N/Asend_as_request(krb5_context context,
2N/A krb5_data *packet,
2N/A krb5_kdc_req *request,
2N/A const krb5_data *realm,
2N/A krb5_error ** ret_err_reply,
2N/A krb5_kdc_rep ** ret_as_reply,
2N/A int *use_master)
2N/A{
2N/A krb5_kdc_rep *as_reply = 0;
2N/A krb5_error_code retval;
2N/A krb5_data reply;
2N/A char k4_version; /* same type as *(krb5_data::data) */
2N/A int tcp_only = 0;
2N/A
2N/A reply.data = 0;
2N/A
2N/A /* set the nonce if the caller expects us to do it */
2N/A
2N/A k4_version = packet->data[0];
2N/Asend_again:
2N/A /*
2N/A * Solaris Kerberos
2N/A * krb5_sendto_kdc takes an extra parameter - "request" which is the
2N/A * krb5_kdc_req encoded in "packet".
2N/A */
2N/A retval = krb5_sendto_kdc(context, packet, request,
2N/A realm,
2N/A &reply, use_master, tcp_only, NULL);
2N/A#if APPLE_PKINIT
2N/A inTktDebug("krb5_sendto_kdc returned %d\n", (int)retval);
2N/A#endif /* APPLE_PKINIT */
2N/A
2N/A if (retval)
2N/A goto cleanup;
2N/A
2N/A /* now decode the reply...could be error or as_rep */
2N/A if (krb5_is_krb_error(&reply)) {
2N/A krb5_error *err_reply;
2N/A
2N/A if ((retval = decode_krb5_error(&reply, &err_reply)))
2N/A /* some other error code--??? */
2N/A goto cleanup;
2N/A
2N/A if (ret_err_reply) {
2N/A if (err_reply->error == KRB_ERR_RESPONSE_TOO_BIG
2N/A && tcp_only == 0) {
2N/A tcp_only = 1;
2N/A krb5_free_error(context, err_reply);
2N/A free(reply.data);
2N/A reply.data = 0;
2N/A /* Solaris Kerberos */
2N/A reply.length = 0;
2N/A goto send_again;
2N/A }
2N/A *ret_err_reply = err_reply;
2N/A } else {
2N/A krb5_free_error(context, err_reply);
2N/A err_reply = NULL; /* Solaris Kerberos */
2N/A }
2N/A goto cleanup;
2N/A }
2N/A
2N/A /*
2N/A * Check to make sure it isn't a V4 reply.
2N/A */
2N/A if (!krb5_is_as_rep(&reply)) {
2N/A/* these are in <kerberosIV/prot.h> as well but it isn't worth including. */
2N/A#define V4_KRB_PROT_VERSION 4
2N/A#define V4_AUTH_MSG_ERR_REPLY (5<<1)
2N/A /* check here for V4 reply */
2N/A unsigned int t_switch;
2N/A
2N/A /* From v4 g_in_tkt.c: This used to be
2N/A switch (pkt_msg_type(rpkt) & ~1) {
2N/A but SCO 3.2v4 cc compiled that incorrectly. */
2N/A t_switch = reply.data[1];
2N/A t_switch &= ~1;
2N/A
2N/A if (t_switch == V4_AUTH_MSG_ERR_REPLY
2N/A && (reply.data[0] == V4_KRB_PROT_VERSION
2N/A || reply.data[0] == k4_version)) {
2N/A retval = KRB5KRB_AP_ERR_V4_REPLY;
2N/A } else {
2N/A retval = KRB5KRB_AP_ERR_MSG_TYPE;
2N/A }
2N/A goto cleanup;
2N/A }
2N/A
2N/A /* It must be a KRB_AS_REP message, or an bad returned packet */
2N/A if ((retval = decode_krb5_as_rep(&reply, &as_reply)))
2N/A /* some other error code ??? */
2N/A goto cleanup;
2N/A
2N/A if (as_reply->msg_type != KRB5_AS_REP) {
2N/A retval = KRB5KRB_AP_ERR_MSG_TYPE;
2N/A krb5_free_kdc_rep(context, as_reply);
2N/A goto cleanup;
2N/A }
2N/A
2N/A if (ret_as_reply)
2N/A *ret_as_reply = as_reply;
2N/A else
2N/A krb5_free_kdc_rep(context, as_reply);
2N/A
2N/Acleanup:
2N/A if (reply.data) {
2N/A free(reply.data);
2N/A /* Solaris Kerberos just to be safe */
2N/A reply.data = NULL;
2N/A reply.length = 0;
2N/A }
2N/Aout: /* Solaris Kerberos for dtrace */
2N/A return retval;
2N/A}
2N/A
2N/Astatic krb5_error_code
2N/Adecrypt_as_reply(krb5_context context,
2N/A krb5_kdc_req *request,
2N/A krb5_kdc_rep *as_reply,
2N/A git_key_proc key_proc,
2N/A krb5_const_pointer keyseed,
2N/A krb5_keyblock * key,
2N/A git_decrypt_proc decrypt_proc,
2N/A krb5_const_pointer decryptarg)
2N/A{
2N/A krb5_error_code retval;
2N/A krb5_keyblock * decrypt_key = 0;
2N/A krb5_data salt;
2N/A
2N/A if (as_reply->enc_part2)
2N/A return 0;
2N/A
2N/A if (key)
2N/A decrypt_key = key;
2N/A else {
2N/A /*
2N/A * Use salt corresponding to the client principal supplied by
2N/A * the KDC, which may differ from the requested principal if
2N/A * canonicalization is in effect. We will check
2N/A * as_reply->client later in verify_as_reply.
2N/A */
2N/A if ((retval = krb5_principal2salt(context, as_reply->client, &salt)))
2N/A return(retval);
2N/A
2N/A retval = (*key_proc)(context, as_reply->enc_part.enctype,
2N/A &salt, keyseed, &decrypt_key);
2N/A free(salt.data);
2N/A if (retval)
2N/A goto cleanup;
2N/A }
2N/A
2N/A if ((retval = (*decrypt_proc)(context, decrypt_key, decryptarg, as_reply)))
2N/A goto cleanup;
2N/A
2N/Acleanup:
2N/A if (!key && decrypt_key)
2N/A krb5_free_keyblock(context, decrypt_key);
2N/A return (retval);
2N/A}
2N/A
2N/A/**
2N/A * Fully anonymous replies include a pa_pkinit_kx padata type including the KDC
2N/A * contribution key. This routine confirms that the session key is of the
2N/A * right form for fully anonymous requests. It is here rather than in the
2N/A * preauth code because the session key cannot be verified until the AS reply
2N/A * is decrypted and the preauth code all runs before the AS reply is decrypted.
2N/A */
2N/Astatic krb5_error_code
2N/Averify_anonymous( krb5_context context, krb5_kdc_req *request,
2N/A krb5_kdc_rep *reply, krb5_keyblock *as_key)
2N/A{
2N/A krb5_error_code ret = 0;
2N/A krb5_pa_data *pa;
2N/A krb5_data scratch;
2N/A krb5_keyblock *kdc_key = NULL, *expected = NULL;
2N/A krb5_enc_data *enc = NULL;
2N/A krb5_keyblock *session = reply->enc_part2->session;
2N/A
2N/A if (!krb5_principal_compare_any_realm(context, request->client,
2N/A krb5_anonymous_principal()))
2N/A return 0; /* Only applies to fully anonymous */
2N/A pa = krb5int_find_pa_data(context, reply->padata, KRB5_PADATA_PKINIT_KX);
2N/A if (pa == NULL)
2N/A goto verification_error;
2N/A scratch.length = pa->length;
2N/A scratch.data = (char *) pa->contents;
2N/A ret = decode_krb5_enc_data( &scratch, &enc);
2N/A if (ret)
2N/A goto cleanup;
2N/A scratch.data = k5alloc(enc->ciphertext.length, &ret);
2N/A if (ret)
2N/A goto cleanup;
2N/A scratch.length = enc->ciphertext.length;
2N/A ret = krb5_c_decrypt(context, as_key, KRB5_KEYUSAGE_PA_PKINIT_KX,
2N/A NULL /*cipherstate*/, enc, &scratch);
2N/A if (ret) {
2N/A free(scratch.data);
2N/A goto cleanup;
2N/A }
2N/A ret = decode_krb5_encryption_key( &scratch, &kdc_key);
2N/A zap(scratch.data, scratch.length);
2N/A free(scratch.data);
2N/A if (ret)
2N/A goto cleanup;
2N/A ret = krb5_c_fx_cf2_simple(context, kdc_key, "PKINIT",
2N/A as_key, "KEYEXCHANGE", &expected);
2N/A if (ret)
2N/A goto cleanup;
2N/A if ((expected->enctype != session->enctype) ||
2N/A (expected->length != session->length) ||
2N/A (memcmp(expected->contents, session->contents, expected->length) != 0))
2N/A goto verification_error;
2N/Acleanup:
2N/A if (kdc_key)
2N/A krb5_free_keyblock(context, kdc_key);
2N/A if (expected)
2N/A krb5_free_keyblock(context, expected);
2N/A if (enc)
2N/A krb5_free_enc_data(context, enc);
2N/A return ret;
2N/Averification_error:
2N/A ret = KRB5_KDCREP_MODIFIED;
2N/A krb5_set_error_message(context, ret, "Reply has wrong form of session key "
2N/A "for anonymous request");
2N/A goto cleanup;
2N/A}
2N/A
2N/Astatic krb5_error_code
2N/Averify_as_reply(krb5_context context,
2N/A krb5_timestamp time_now,
2N/A krb5_kdc_req *request,
2N/A krb5_kdc_rep *as_reply)
2N/A{
2N/A krb5_error_code retval;
2N/A int canon_req;
2N/A int canon_ok;
2N/A
2N/A /* check the contents for sanity: */
2N/A if (!as_reply->enc_part2->times.starttime)
2N/A as_reply->enc_part2->times.starttime =
2N/A as_reply->enc_part2->times.authtime;
2N/A
2N/A /*
2N/A * We only allow the AS-REP server name to be changed if the
2N/A * caller set the canonicalize flag (or requested an enterprise
2N/A * principal) and we requested (and received) a TGT.
2N/A */
2N/A canon_req = ((request->kdc_options & KDC_OPT_CANONICALIZE) != 0) ||
2N/A (krb5_princ_type(context, request->client) ==
2N/A KRB5_NT_ENTERPRISE_PRINCIPAL) ||
2N/A (request->kdc_options & KDC_OPT_REQUEST_ANONYMOUS);
2N/A if (canon_req) {
2N/A canon_ok = IS_TGS_PRINC(context, request->server) &&
2N/A IS_TGS_PRINC(context, as_reply->enc_part2->server);
2N/A if (!canon_ok && (request->kdc_options & KDC_OPT_REQUEST_ANONYMOUS)) {
2N/A canon_ok = krb5_principal_compare_any_realm(context,
2N/A as_reply->client,
2N/A krb5_anonymous_principal());
2N/A }
2N/A } else
2N/A canon_ok = 0;
2N/A
2N/A if ((!canon_ok &&
2N/A (!krb5_principal_compare(context, as_reply->client, request->client) ||
2N/A !krb5_principal_compare(context, as_reply->enc_part2->server, request->server)))
2N/A || !krb5_principal_compare(context, as_reply->enc_part2->server, as_reply->ticket->server)
2N/A || (request->nonce != as_reply->enc_part2->nonce)
2N/A /* XXX check for extraneous flags */
2N/A /* XXX || (!krb5_addresses_compare(context, addrs, as_reply->enc_part2->caddrs)) */
2N/A || ((request->kdc_options & KDC_OPT_POSTDATED) &&
2N/A (request->from != 0) &&
2N/A (request->from != as_reply->enc_part2->times.starttime))
2N/A || ((request->till != 0) &&
2N/A (as_reply->enc_part2->times.endtime > request->till))
2N/A || ((request->kdc_options & KDC_OPT_RENEWABLE) &&
2N/A /*
2N/A * Solaris Kerberos: Here we error only if renewable_ok was not set.
2N/A */
2N/A !(request->kdc_options & KDC_OPT_RENEWABLE_OK) &&
2N/A (as_reply->enc_part2->flags & KDC_OPT_RENEWABLE) &&
2N/A (request->rtime != 0) &&
2N/A (as_reply->enc_part2->times.renew_till > request->rtime))
2N/A || ((request->kdc_options & KDC_OPT_RENEWABLE_OK) &&
2N/A !(request->kdc_options & KDC_OPT_RENEWABLE) &&
2N/A (as_reply->enc_part2->flags & KDC_OPT_RENEWABLE) &&
2N/A (request->till != 0) &&
2N/A (as_reply->enc_part2->times.renew_till > request->till))
2N/A /*
2N/A * Solaris Kerberos: renew_till should never be greater than till or
2N/A * rtime.
2N/A */
2N/A || ((request->kdc_options & KDC_OPT_RENEWABLE_OK) &&
2N/A (as_reply->enc_part2->flags & KDC_OPT_RENEWABLE) &&
2N/A (request->till != 0) &&
2N/A (request->rtime != 0) &&
2N/A (as_reply->enc_part2->times.renew_till > max(request->till,
2N/A request->rtime)))
2N/A ) {
2N/A#if APPLE_PKINIT
2N/A inTktDebug("verify_as_reply: KDCREP_MODIFIED\n");
2N/A#if IN_TKT_DEBUG
2N/A if(request->client->realm.length && request->client->data->length)
2N/A inTktDebug("request: name %s realm %s\n",
2N/A request->client->realm.data, request->client->data->data);
2N/A if(as_reply->client->realm.length && as_reply->client->data->length)
2N/A inTktDebug("reply : name %s realm %s\n",
2N/A as_reply->client->realm.data, as_reply->client->data->data);
2N/A#endif
2N/A#endif /* APPLE_PKINIT */
2N/A return KRB5_KDCREP_MODIFIED;
2N/A }
2N/A
2N/A if (context->library_options & KRB5_LIBOPT_SYNC_KDCTIME) {
2N/A retval = krb5_set_real_time(context,
2N/A as_reply->enc_part2->times.authtime, -1);
2N/A if (retval)
2N/A return retval;
2N/A } else {
2N/A if ((request->from == 0) &&
2N/A (labs(as_reply->enc_part2->times.starttime - time_now)
2N/A > context->clockskew))
2N/A return (KRB5_KDCREP_SKEW);
2N/A }
2N/A return 0;
2N/A}
2N/A
2N/Astatic krb5_error_code
2N/Astash_as_reply(krb5_context context,
2N/A krb5_timestamp time_now,
2N/A krb5_kdc_req *request,
2N/A krb5_kdc_rep *as_reply,
2N/A krb5_creds * creds,
2N/A krb5_ccache ccache)
2N/A{
2N/A krb5_error_code retval;
2N/A krb5_data * packet;
2N/A krb5_principal client;
2N/A krb5_principal server;
2N/A
2N/A client = NULL;
2N/A server = NULL;
2N/A
2N/A if (!creds->client)
2N/A if ((retval = krb5_copy_principal(context, as_reply->client, &client)))
2N/A goto cleanup;
2N/A
2N/A if (!creds->server)
2N/A if ((retval = krb5_copy_principal(context, as_reply->enc_part2->server,
2N/A &server)))
2N/A goto cleanup;
2N/A
2N/A /* fill in the credentials */
2N/A if ((retval = krb5_copy_keyblock_contents(context,
2N/A as_reply->enc_part2->session,
2N/A &creds->keyblock)))
2N/A goto cleanup;
2N/A
2N/A creds->times = as_reply->enc_part2->times;
2N/A creds->is_skey = FALSE; /* this is an AS_REQ, so cannot
2N/A be encrypted in skey */
2N/A creds->ticket_flags = as_reply->enc_part2->flags;
2N/A if ((retval = krb5_copy_addresses(context, as_reply->enc_part2->caddrs,
2N/A &creds->addresses)))
2N/A goto cleanup;
2N/A
2N/A creds->second_ticket.length = 0;
2N/A creds->second_ticket.data = 0;
2N/A
2N/A if ((retval = encode_krb5_ticket(as_reply->ticket, &packet)))
2N/A goto cleanup;
2N/A
2N/A creds->ticket = *packet;
2N/A free(packet);
2N/A
2N/A /* store it in the ccache! */
2N/A if (ccache)
2N/A if ((retval = krb5_cc_store_cred(context, ccache, creds)))
2N/A goto cleanup;
2N/A
2N/A if (!creds->client)
2N/A creds->client = client;
2N/A if (!creds->server)
2N/A creds->server = server;
2N/A
2N/Acleanup:
2N/A if (retval) {
2N/A if (client)
2N/A krb5_free_principal(context, client);
2N/A if (server)
2N/A krb5_free_principal(context, server);
2N/A if (creds->keyblock.contents) {
2N/A memset(creds->keyblock.contents, 0,
2N/A creds->keyblock.length);
2N/A free(creds->keyblock.contents);
2N/A creds->keyblock.contents = 0;
2N/A creds->keyblock.length = 0;
2N/A }
2N/A if (creds->ticket.data) {
2N/A free(creds->ticket.data);
2N/A creds->ticket.data = 0;
2N/A }
2N/A if (creds->addresses) {
2N/A krb5_free_addresses(context, creds->addresses);
2N/A creds->addresses = 0;
2N/A }
2N/A }
2N/A return (retval);
2N/A}
2N/A
2N/Astatic krb5_error_code
2N/Amake_preauth_list(krb5_context context,
2N/A krb5_preauthtype * ptypes,
2N/A int nptypes,
2N/A krb5_pa_data *** ret_list)
2N/A{
2N/A krb5_preauthtype * ptypep;
2N/A krb5_pa_data ** preauthp;
2N/A int i;
2N/A
2N/A if (nptypes < 0) {
2N/A for (nptypes=0, ptypep = ptypes; *ptypep; ptypep++, nptypes++)
2N/A ;
2N/A }
2N/A
2N/A /* allocate space for a NULL to terminate the list */
2N/A
2N/A if ((preauthp =
2N/A (krb5_pa_data **) malloc((nptypes+1)*sizeof(krb5_pa_data *))) == NULL)
2N/A return(ENOMEM);
2N/A
2N/A for (i=0; i<nptypes; i++) {
2N/A if ((preauthp[i] =
2N/A (krb5_pa_data *) malloc(sizeof(krb5_pa_data))) == NULL) {
2N/A for (; i>=0; i--)
2N/A free(preauthp[i]);
2N/A free(preauthp);
2N/A return (ENOMEM);
2N/A }
2N/A preauthp[i]->magic = KV5M_PA_DATA;
2N/A preauthp[i]->pa_type = ptypes[i];
2N/A preauthp[i]->length = 0;
2N/A preauthp[i]->contents = 0;
2N/A }
2N/A
2N/A /* fill in the terminating NULL */
2N/A
2N/A preauthp[nptypes] = NULL;
2N/A
2N/A *ret_list = preauthp;
2N/A return 0;
2N/A}
2N/A
2N/A#define MAX_IN_TKT_LOOPS 16
2N/Astatic const krb5_enctype get_in_tkt_enctypes[] = {
2N/A ENCTYPE_DES3_CBC_SHA1,
2N/A ENCTYPE_ARCFOUR_HMAC,
2N/A ENCTYPE_DES_CBC_MD5,
2N/A ENCTYPE_DES_CBC_MD4,
2N/A ENCTYPE_DES_CBC_CRC,
2N/A 0
2N/A};
2N/A
2N/Astatic krb5_error_code
2N/Arewrite_server_realm(krb5_context context,
2N/A krb5_const_principal old_server,
2N/A const krb5_data *realm,
2N/A krb5_boolean tgs,
2N/A krb5_principal *server)
2N/A{
2N/A krb5_error_code retval;
2N/A
2N/A assert(*server == NULL);
2N/A
2N/A retval = krb5_copy_principal(context, old_server, server);
2N/A if (retval)
2N/A return retval;
2N/A
2N/A krb5_free_data_contents(context, &(*server)->realm);
2N/A (*server)->realm.data = NULL;
2N/A
2N/A retval = krb5int_copy_data_contents(context, realm, &(*server)->realm);
2N/A if (retval)
2N/A goto cleanup;
2N/A
2N/A if (tgs) {
2N/A krb5_free_data_contents(context, &(*server)->data[1]);
2N/A (*server)->data[1].data = NULL;
2N/A
2N/A retval = krb5int_copy_data_contents(context, realm, &(*server)->data[1]);
2N/A if (retval)
2N/A goto cleanup;
2N/A }
2N/A
2N/Acleanup:
2N/A if (retval) {
2N/A krb5_free_principal(context, *server);
2N/A *server = NULL;
2N/A }
2N/A
2N/A return retval;
2N/A}
2N/A
2N/Astatic inline int
2N/Atgt_is_local_realm(krb5_creds *tgt)
2N/A{
2N/A return (tgt->server->length == 2
2N/A && data_eq_string(tgt->server->data[0], KRB5_TGS_NAME)
2N/A && data_eq(tgt->server->data[1], tgt->client->realm)
2N/A && data_eq(tgt->server->realm, tgt->client->realm));
2N/A}
2N/A
2N/Astatic krb5_error_code
2N/Arequest_enc_pa_rep(krb5_pa_data ***padptr)
2N/A{
2N/A size_t size = 0;
2N/A krb5_pa_data **pad = *padptr;
2N/A krb5_pa_data *pa= NULL;
2N/A if (pad)
2N/A for (size=0; pad[size]; size++);
2N/A pad = realloc(pad, sizeof(*pad)*(size+2));
2N/A
2N/A if (pad == NULL)
2N/A return ENOMEM;
2N/A pad[size+1] = NULL;
2N/A pa = malloc(sizeof(krb5_pa_data));
2N/A if (pa == NULL)
2N/A return ENOMEM;
2N/A pa->contents = NULL;
2N/A pa->length = 0;
2N/A pa->pa_type = KRB5_ENCPADATA_REQ_ENC_PA_REP;
2N/A pad[size] = pa;
2N/A *padptr = pad;
2N/A return 0;
2N/A}
2N/A
2N/Akrb5_error_code KRB5_CALLCONV
2N/Akrb5_get_in_tkt(krb5_context context,
2N/A krb5_flags options,
2N/A krb5_address * const * addrs,
2N/A krb5_enctype * ktypes,
2N/A krb5_preauthtype * ptypes,
2N/A git_key_proc key_proc,
2N/A krb5_const_pointer keyseed,
2N/A git_decrypt_proc decrypt_proc,
2N/A krb5_const_pointer decryptarg,
2N/A krb5_creds * creds,
2N/A krb5_ccache ccache,
2N/A krb5_kdc_rep ** ret_as_reply)
2N/A{
2N/A krb5_error_code retval;
2N/A krb5_timestamp time_now;
2N/A krb5_keyblock * decrypt_key = 0;
2N/A krb5_kdc_req request;
2N/A krb5_data *encoded_request;
2N/A krb5_error * err_reply;
2N/A krb5_kdc_rep * as_reply = 0;
2N/A krb5_pa_data ** preauth_to_use = 0;
2N/A int loopcount = 0;
2N/A krb5_int32 do_more = 0;
2N/A int canon_flag;
2N/A int use_master = 0;
2N/A int referral_count = 0;
2N/A krb5_principal_data referred_client;
2N/A krb5_principal referred_server = NULL;
2N/A krb5_boolean is_tgt_req;
2N/A
2N/A#if APPLE_PKINIT
2N/A inTktDebug("krb5_get_in_tkt top\n");
2N/A#endif /* APPLE_PKINIT */
2N/A
2N/A if (! krb5_realm_compare(context, creds->client, creds->server)) {
2N/A /* Solaris Kerberos better errors */
2N/A char *s_name = NULL;
2N/A char *c_name = NULL;
2N/A krb5_error_code serr, cerr;
2N/A serr = krb5_unparse_name(context, creds->server, &s_name);
2N/A cerr = krb5_unparse_name(context, creds->client, &c_name);
2N/A krb5_set_error_message(context, KRB5_IN_TKT_REALM_MISMATCH,
2N/A dgettext(TEXT_DOMAIN,
2N/A "Client/server realm mismatch in "
2N/A "initial ticket request: '%s' "
2N/A "requesting ticket '%s'"),
2N/A cerr ? "unknown" : c_name,
2N/A serr ? "unknown" : s_name);
2N/A if (s_name)
2N/A krb5_free_unparsed_name(context, s_name);
2N/A if (c_name)
2N/A krb5_free_unparsed_name(context, c_name);
2N/A return KRB5_IN_TKT_REALM_MISMATCH;
2N/A }
2N/A
2N/A if (ret_as_reply)
2N/A *ret_as_reply = 0;
2N/A
2N/A referred_client = *(creds->client);
2N/A referred_client.realm.data = NULL;
2N/A referred_client.realm.length = 0;
2N/A
2N/A /* per referrals draft, enterprise principals imply canonicalization */
2N/A canon_flag = ((options & KDC_OPT_CANONICALIZE) != 0) ||
2N/A creds->client->type == KRB5_NT_ENTERPRISE_PRINCIPAL;
2N/A
2N/A /*
2N/A * Set up the basic request structure
2N/A */
2N/A request.magic = KV5M_KDC_REQ;
2N/A request.msg_type = KRB5_AS_REQ;
2N/A request.addresses = 0;
2N/A request.ktype = 0;
2N/A request.padata = 0;
2N/A if (addrs)
2N/A request.addresses = (krb5_address **) addrs;
2N/A else
2N/A if ((retval = krb5_os_localaddr(context, &request.addresses)))
2N/A goto cleanup;
2N/A request.kdc_options = options;
2N/A request.client = creds->client;
2N/A request.server = creds->server;
2N/A request.nonce = 0;
2N/A request.from = creds->times.starttime;
2N/A request.till = creds->times.endtime;
2N/A request.rtime = creds->times.renew_till;
2N/A#if APPLE_PKINIT
2N/A retval = gen_nonce(context, (krb5_int32 *)&time_now);
2N/A if(retval) {
2N/A goto cleanup;
2N/A }
2N/A request.nonce = time_now;
2N/A#endif /* APPLE_PKINIT */
2N/A
2N/A retval = krb5int_copy_etypes(get_in_tkt_enctypes, &request.ktype);
2N/A request.nktypes = krb5int_count_etypes(request.ktype);
2N/A if (ktypes) {
2N/A int i, req, next = 0;
2N/A for (req = 0; ktypes[req]; req++) {
2N/A if (ktypes[req] == request.ktype[next]) {
2N/A next++;
2N/A continue;
2N/A }
2N/A for (i = next + 1; i < request.nktypes; i++)
2N/A if (ktypes[req] == request.ktype[i]) {
2N/A /* Found the enctype we want, but not in the
2N/A position we want. Move it, but keep the old
2N/A one from the desired slot around in case it's
2N/A later in our requested-ktypes list. */
2N/A krb5_enctype t;
2N/A t = request.ktype[next];
2N/A request.ktype[next] = request.ktype[i];
2N/A request.ktype[i] = t;
2N/A next++;
2N/A break;
2N/A }
2N/A /* If we didn't find it, don't do anything special, just
2N/A drop it. */
2N/A }
2N/A request.ktype[next] = 0;
2N/A request.nktypes = next;
2N/A }
2N/A request.authorization_data.ciphertext.length = 0;
2N/A request.authorization_data.ciphertext.data = 0;
2N/A request.unenc_authdata = 0;
2N/A request.second_ticket = 0;
2N/A
2N/A /*
2N/A * If a list of preauth types are passed in, convert it to a
2N/A * preauth_to_use list.
2N/A */
2N/A if (ptypes) {
2N/A retval = make_preauth_list(context, ptypes, -1, &preauth_to_use);
2N/A if (retval)
2N/A goto cleanup;
2N/A }
2N/A
2N/A is_tgt_req = tgt_is_local_realm(creds);
2N/A
2N/A while (1) {
2N/A if (loopcount++ > MAX_IN_TKT_LOOPS) {
2N/A retval = KRB5_GET_IN_TKT_LOOP;
2N/A /* Solaris Kerberos better errors */
2N/A {
2N/A char *s_name = NULL;
2N/A char *c_name = NULL;
2N/A krb5_error_code serr, cerr;
2N/A serr = krb5_unparse_name(context, creds->server, &s_name);
2N/A cerr = krb5_unparse_name(context, creds->client, &c_name);
2N/A krb5_set_error_message(context, retval,
2N/A dgettext(TEXT_DOMAIN,
2N/A "Looping detected getting "
2N/A "ticket: '%s' requesting "
2N/A "ticket '%s'. Max loops is "
2N/A "%d. Make sure a KDC is "
2N/A "available"),
2N/A cerr ? "unknown" : c_name,
2N/A serr ? "unknown" : s_name,
2N/A MAX_IN_TKT_LOOPS);
2N/A if (s_name)
2N/A krb5_free_unparsed_name(context, s_name);
2N/A if (c_name)
2N/A krb5_free_unparsed_name(context, c_name);
2N/A }
2N/A goto cleanup;
2N/A }
2N/A
2N/A#if APPLE_PKINIT
2N/A inTktDebug("krb5_get_in_tkt calling krb5_obtain_padata\n");
2N/A#endif /* APPLE_PKINIT */
2N/A if ((retval = krb5_obtain_padata(context, preauth_to_use, key_proc,
2N/A keyseed, creds, &request)) != 0)
2N/A goto cleanup;
2N/A if (preauth_to_use)
2N/A krb5_free_pa_data(context, preauth_to_use);
2N/A preauth_to_use = 0;
2N/A
2N/A err_reply = 0;
2N/A as_reply = 0;
2N/A
2N/A if ((retval = krb5_timeofday(context, &time_now)))
2N/A goto cleanup;
2N/A
2N/A /*
2N/A * XXX we know they are the same size... and we should do
2N/A * something better than just the current time
2N/A */
2N/A request.nonce = (krb5_int32) time_now;
2N/A
2N/A if ((retval = encode_krb5_as_req(&request, &encoded_request)) != 0)
2N/A goto cleanup;
2N/A retval = send_as_request(context, encoded_request, &request,
2N/A krb5_princ_realm(context, request.client), &err_reply,
2N/A &as_reply, &use_master);
2N/A krb5_free_data(context, encoded_request);
2N/A if (retval != 0)
2N/A goto cleanup;
2N/A
2N/A if (err_reply) {
2N/A if (err_reply->error == KDC_ERR_PREAUTH_REQUIRED &&
2N/A err_reply->e_data.length > 0) {
2N/A retval = decode_krb5_padata_sequence(&err_reply->e_data,
2N/A &preauth_to_use);
2N/A krb5_free_error(context, err_reply);
2N/A if (retval)
2N/A goto cleanup;
2N/A retval = sort_krb5_padata_sequence(context,
2N/A &request.server->realm,
2N/A preauth_to_use);
2N/A if (retval)
2N/A goto cleanup;
2N/A continue;
2N/A } else if (canon_flag && err_reply->error == KDC_ERR_WRONG_REALM) {
2N/A if (++referral_count > KRB5_REFERRAL_MAXHOPS ||
2N/A err_reply->client == NULL ||
2N/A err_reply->client->realm.length == 0) {
2N/A retval = KRB5KDC_ERR_WRONG_REALM;
2N/A krb5_free_error(context, err_reply);
2N/A goto cleanup;
2N/A }
2N/A /* Rewrite request.client with realm from error reply */
2N/A if (referred_client.realm.data) {
2N/A krb5_free_data_contents(context, &referred_client.realm);
2N/A referred_client.realm.data = NULL;
2N/A }
2N/A retval = krb5int_copy_data_contents(context,
2N/A &err_reply->client->realm,
2N/A &referred_client.realm);
2N/A krb5_free_error(context, err_reply);
2N/A if (retval)
2N/A goto cleanup;
2N/A request.client = &referred_client;
2N/A
2N/A if (referred_server != NULL) {
2N/A krb5_free_principal(context, referred_server);
2N/A referred_server = NULL;
2N/A }
2N/A
2N/A retval = rewrite_server_realm(context,
2N/A creds->server,
2N/A &referred_client.realm,
2N/A is_tgt_req,
2N/A &referred_server);
2N/A if (retval)
2N/A goto cleanup;
2N/A request.server = referred_server;
2N/A
2N/A continue;
2N/A } else {
2N/A retval = (krb5_error_code) err_reply->error
2N/A + ERROR_TABLE_BASE_krb5;
2N/A krb5_free_error(context, err_reply);
2N/A goto cleanup;
2N/A }
2N/A } else if (!as_reply) {
2N/A retval = KRB5KRB_AP_ERR_MSG_TYPE;
2N/A goto cleanup;
2N/A }
2N/A if ((retval = krb5_process_padata(context, &request, as_reply,
2N/A key_proc, keyseed, decrypt_proc,
2N/A &decrypt_key, creds,
2N/A &do_more)) != 0)
2N/A goto cleanup;
2N/A
2N/A if (!do_more)
2N/A break;
2N/A }
2N/A
2N/A if ((retval = decrypt_as_reply(context, &request, as_reply, key_proc,
2N/A keyseed, decrypt_key, decrypt_proc,
2N/A decryptarg)))
2N/A goto cleanup;
2N/A
2N/A if ((retval = verify_as_reply(context, time_now, &request, as_reply)))
2N/A goto cleanup;
2N/A
2N/A if ((retval = stash_as_reply(context, time_now, &request, as_reply,
2N/A creds, ccache)))
2N/A goto cleanup;
2N/A
2N/Acleanup:
2N/A if (request.ktype)
2N/A free(request.ktype);
2N/A if (!addrs && request.addresses)
2N/A krb5_free_addresses(context, request.addresses);
2N/A if (request.padata)
2N/A krb5_free_pa_data(context, request.padata);
2N/A if (preauth_to_use)
2N/A krb5_free_pa_data(context, preauth_to_use);
2N/A if (decrypt_key)
2N/A krb5_free_keyblock(context, decrypt_key);
2N/A if (as_reply) {
2N/A if (ret_as_reply)
2N/A *ret_as_reply = as_reply;
2N/A else
2N/A krb5_free_kdc_rep(context, as_reply);
2N/A }
2N/A if (referred_client.realm.data)
2N/A krb5_free_data_contents(context, &referred_client.realm);
2N/A if (referred_server)
2N/A krb5_free_principal(context, referred_server);
2N/A return (retval);
2N/A}
2N/A
2N/A/* Sort a pa_data sequence so that types named in the "preferred_preauth_types"
2N/A * libdefaults entry are listed before any others. */
2N/Astatic krb5_error_code
2N/Asort_krb5_padata_sequence(krb5_context context, krb5_data *realm,
2N/A krb5_pa_data **padata)
2N/A{
2N/A int i, j, base;
2N/A krb5_error_code ret;
2N/A const char *p;
2N/A long l;
2N/A char *q, *preauth_types = NULL;
2N/A krb5_pa_data *tmp;
2N/A int need_free_string = 1;
2N/A
2N/A if ((padata == NULL) || (padata[0] == NULL)) {
2N/A return 0;
2N/A }
2N/A
2N/A ret = krb5int_libdefault_string(context, realm, KRB5_CONF_PREFERRED_PREAUTH_TYPES,
2N/A &preauth_types);
2N/A if ((ret != 0) || (preauth_types == NULL)) {
2N/A /* Try to use PKINIT first. */
2N/A preauth_types = "17, 16, 15, 14";
2N/A need_free_string = 0;
2N/A }
2N/A
2N/A#ifdef DEBUG
2N/A fprintf (stderr, "preauth data types before sorting:");
2N/A for (i = 0; padata[i]; i++) {
2N/A fprintf (stderr, " %d", padata[i]->pa_type);
2N/A }
2N/A fprintf (stderr, "\n");
2N/A#endif
2N/A
2N/A base = 0;
2N/A for (p = preauth_types; *p != '\0';) {
2N/A /* skip whitespace to find an entry */
2N/A p += strspn(p, ", ");
2N/A if (*p != '\0') {
2N/A /* see if we can extract a number */
2N/A l = strtol(p, &q, 10);
2N/A if ((q != NULL) && (q > p)) {
2N/A /* got a valid number; search for a matchin entry */
2N/A for (i = base; padata[i] != NULL; i++) {
2N/A /* bubble the matching entry to the front of the list */
2N/A if (padata[i]->pa_type == l) {
2N/A tmp = padata[i];
2N/A for (j = i; j > base; j--)
2N/A padata[j] = padata[j - 1];
2N/A padata[base] = tmp;
2N/A base++;
2N/A break;
2N/A }
2N/A }
2N/A p = q;
2N/A } else {
2N/A break;
2N/A }
2N/A }
2N/A }
2N/A if (need_free_string)
2N/A free(preauth_types);
2N/A
2N/A#ifdef DEBUG
2N/A fprintf (stderr, "preauth data types after sorting:");
2N/A for (i = 0; padata[i]; i++)
2N/A fprintf (stderr, " %d", padata[i]->pa_type);
2N/A fprintf (stderr, "\n");
2N/A#endif
2N/A
2N/A return 0;
2N/A}
2N/A
2N/Astatic krb5_error_code
2N/Abuild_in_tkt_name(krb5_context context,
2N/A char *in_tkt_service,
2N/A krb5_const_principal client,
2N/A krb5_principal *server)
2N/A{
2N/A krb5_error_code ret;
2N/A
2N/A *server = NULL;
2N/A
2N/A if (in_tkt_service) {
2N/A /* this is ugly, because so are the data structures involved. I'm
2N/A in the library, so I'm going to manipulate the data structures
2N/A directly, otherwise, it will be worse. */
2N/A
2N/A if ((ret = krb5_parse_name(context, in_tkt_service, server)))
2N/A return ret;
2N/A
2N/A /* stuff the client realm into the server principal.
2N/A realloc if necessary */
2N/A if ((*server)->realm.length < client->realm.length) {
2N/A char *p = realloc((*server)->realm.data,
2N/A client->realm.length);
2N/A if (p == NULL) {
2N/A krb5_free_principal(context, *server);
2N/A *server = NULL;
2N/A return ENOMEM;
2N/A }
2N/A (*server)->realm.data = p;
2N/A }
2N/A
2N/A (*server)->realm.length = client->realm.length;
2N/A memcpy((*server)->realm.data, client->realm.data, client->realm.length);
2N/A } else {
2N/A ret = krb5_build_principal_ext(context, server,
2N/A client->realm.length,
2N/A client->realm.data,
2N/A KRB5_TGS_NAME_SIZE,
2N/A KRB5_TGS_NAME,
2N/A client->realm.length,
2N/A client->realm.data,
2N/A 0);
2N/A }
2N/A return ret;
2N/A}
2N/A
2N/Avoid KRB5_CALLCONV
2N/Akrb5_init_creds_free(krb5_context context,
2N/A krb5_init_creds_context ctx)
2N/A{
2N/A if (ctx == NULL)
2N/A return;
2N/A
2N/A if (ctx->opte != NULL && krb5_gic_opt_is_shadowed(ctx->opte)) {
2N/A krb5_get_init_creds_opt_free(context,
2N/A (krb5_get_init_creds_opt *)ctx->opte);
2N/A }
2N/A free(ctx->in_tkt_service);
2N/A zap(ctx->password.data, ctx->password.length);
2N/A krb5_free_data_contents(context, &ctx->password);
2N/A krb5_free_error(context, ctx->err_reply);
2N/A krb5_free_cred_contents(context, &ctx->cred);
2N/A krb5_free_kdc_req(context, ctx->request);
2N/A krb5_free_kdc_rep(context, ctx->reply);
2N/A krb5_free_data(context, ctx->encoded_request_body);
2N/A krb5_free_data(context, ctx->encoded_previous_request);
2N/A krb5int_fast_free_state(context, ctx->fast_state);
2N/A krb5_free_pa_data(context, ctx->preauth_to_use);
2N/A krb5_free_data_contents(context, &ctx->salt);
2N/A krb5_free_data_contents(context, &ctx->s2kparams);
2N/A krb5_free_keyblock_contents(context, &ctx->as_key);
2N/A free(ctx);
2N/A}
2N/A
2N/Astatic krb5_error_code
2N/Ainit_creds_get(krb5_context context,
2N/A krb5_init_creds_context ctx,
2N/A int *use_master)
2N/A{
2N/A krb5_error_code code;
2N/A krb5_data request;
2N/A krb5_data reply;
2N/A krb5_data realm;
2N/A unsigned int flags = 0;
2N/A int tcp_only = 0;
2N/A char *hostname_used = NULL; /* Solaris Kerberos better errors */
2N/A
2N/A request.length = 0;
2N/A request.data = NULL;
2N/A reply.length = 0;
2N/A reply.data = NULL;
2N/A realm.length = 0;
2N/A realm.data = NULL;
2N/A
2N/A for (;;) {
2N/A code = krb5_init_creds_step(context,
2N/A ctx,
2N/A &reply,
2N/A &request,
2N/A &realm,
2N/A &flags,
2N/A hostname_used);
2N/A if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG && !tcp_only)
2N/A tcp_only = 1;
2N/A else if (code != 0 || (flags & 1) == 0)
2N/A break;
2N/A
2N/A krb5_free_data_contents(context, &reply);
2N/A
2N/A /* Solaris Kerberos */
2N/A code = krb5_sendto_kdc(context, &request, ctx->request, &realm,
2N/A &reply, use_master, tcp_only,
2N/A &hostname_used);
2N/A if (code != 0)
2N/A break;
2N/A
2N/A /* Solaris Kerberos: mem leak fix */
2N/A if (ctx->request->padata) {
2N/A krb5_free_pa_data(context, ctx->request->padata);
2N/A ctx->request->padata = NULL;
2N/A }
2N/A
2N/A krb5_free_data_contents(context, &request);
2N/A krb5_free_data_contents(context, &realm);
2N/A /* Solaris Kerberos */
2N/A free(hostname_used);
2N/A hostname_used = NULL;
2N/A }
2N/A
2N/A krb5_free_data_contents(context, &request);
2N/A krb5_free_data_contents(context, &reply);
2N/A krb5_free_data_contents(context, &realm);
2N/A /* Solaris Kerberos */
2N/A free(hostname_used);
2N/A
2N/A return code;
2N/A}
2N/A
2N/A/* Heimdal API */
2N/Akrb5_error_code KRB5_CALLCONV
2N/Akrb5_init_creds_get(krb5_context context,
2N/A krb5_init_creds_context ctx)
2N/A{
2N/A int use_master = 0;
2N/A
2N/A return init_creds_get(context, ctx, &use_master);
2N/A}
2N/A
2N/Akrb5_error_code KRB5_CALLCONV
2N/Akrb5_init_creds_get_creds(krb5_context context,
2N/A krb5_init_creds_context ctx,
2N/A krb5_creds *creds)
2N/A{
2N/A if ((ctx->flags & KRB5_INIT_CREDS_STEP_FLAG_COMPLETE) == 0)
2N/A return KRB5_NO_TKT_SUPPLIED;
2N/A
2N/A return krb5int_copy_creds_contents(context, &ctx->cred, creds);
2N/A}
2N/A
2N/Akrb5_error_code KRB5_CALLCONV
2N/Akrb5_init_creds_get_times(krb5_context context,
2N/A krb5_init_creds_context ctx,
2N/A krb5_ticket_times *times)
2N/A{
2N/A if ((ctx->flags & KRB5_INIT_CREDS_STEP_FLAG_COMPLETE) == 0)
2N/A return KRB5_NO_TKT_SUPPLIED;
2N/A
2N/A *times = ctx->cred.times;
2N/A
2N/A return 0;
2N/A}
2N/A
2N/Akrb5_error_code KRB5_CALLCONV
2N/Akrb5_init_creds_get_error(krb5_context context,
2N/A krb5_init_creds_context ctx,
2N/A krb5_error **error)
2N/A{
2N/A krb5_error_code code;
2N/A krb5_error *ret = NULL;
2N/A
2N/A *error = NULL;
2N/A
2N/A if (ctx->err_reply == NULL)
2N/A return 0;
2N/A
2N/A ret = k5alloc(sizeof(*ret), &code);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A
2N/A ret->magic = KV5M_ERROR;
2N/A ret->ctime = ctx->err_reply->ctime;
2N/A ret->cusec = ctx->err_reply->cusec;
2N/A ret->susec = ctx->err_reply->susec;
2N/A ret->stime = ctx->err_reply->stime;
2N/A ret->error = ctx->err_reply->error;
2N/A
2N/A if (ctx->err_reply->client != NULL) {
2N/A code = krb5_copy_principal(context, ctx->err_reply->client,
2N/A &ret->client);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A }
2N/A
2N/A code = krb5_copy_principal(context, ctx->err_reply->server, &ret->server);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A
2N/A code = krb5int_copy_data_contents(context, &ctx->err_reply->text,
2N/A &ret->text);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A
2N/A code = krb5int_copy_data_contents(context, &ctx->err_reply->e_data,
2N/A &ret->e_data);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A
2N/A *error = ret;
2N/A
2N/Acleanup:
2N/A if (code != 0)
2N/A krb5_free_error(context, ret);
2N/A
2N/A return code;
2N/A}
2N/A
2N/A/**
2N/A * Throw away any state related to specific realm either at the beginning of a
2N/A * request, or when a realm changes, or when we start to use FAST after
2N/A * assuming we would not do so.
2N/A *
2N/A * @param padata padata from an error if an error from the realm we now expect
2N/A * to talk to caused the restart. Used to infer negotiation characteristics
2N/A * such as whether FAST is used.
2N/A */
2N/Astatic krb5_error_code
2N/Arestart_init_creds_loop(krb5_context context, krb5_init_creds_context ctx,
2N/A krb5_pa_data **padata)
2N/A{
2N/A krb5_error_code code = 0;
2N/A unsigned char random_buf[4];
2N/A krb5_data random_data;
2N/A if (ctx->preauth_to_use) {
2N/A krb5_free_pa_data(context, ctx->preauth_to_use);
2N/A ctx->preauth_to_use = NULL;
2N/A }
2N/A
2N/A if (ctx->fast_state) {
2N/A krb5int_fast_free_state(context, ctx->fast_state);
2N/A ctx->fast_state = NULL;
2N/A }
2N/A code = krb5int_fast_make_state(context, &ctx->fast_state);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A ctx->get_data_rock.fast_state = ctx->fast_state;
2N/A krb5_preauth_request_context_init(context);
2N/A if (ctx->encoded_request_body) {
2N/A krb5_free_data(context, ctx->encoded_request_body);
2N/A ctx->encoded_request_body = NULL;
2N/A }
2N/A if (ctx->opte &&
2N/A (ctx->opte->flags & KRB5_GET_INIT_CREDS_OPT_PREAUTH_LIST)) {
2N/A if ((code = make_preauth_list(context, ctx->opte->preauth_list,
2N/A ctx->opte->preauth_list_length,
2N/A &ctx->preauth_to_use)))
2N/A goto cleanup;
2N/A }
2N/A
2N/A /* Set the request nonce. */
2N/A random_data.length = 4;
2N/A random_data.data = (char *)random_buf;
2N/A code = krb5_c_random_make_octets(context, &random_data);
2N/A if (code !=0)
2N/A goto cleanup;
2N/A /*
2N/A * See RT ticket 3196 at MIT. If we set the high bit, we may have
2N/A * compatibility problems with Heimdal, because we (incorrectly) encode
2N/A * this value as signed.
2N/A */
2N/A ctx->request->nonce = 0x7fffffff & load_32_n(random_buf);
2N/A krb5_free_principal(context, ctx->request->server);
2N/A ctx->request->server = NULL;
2N/A
2N/A code = build_in_tkt_name(context, ctx->in_tkt_service,
2N/A ctx->request->client,
2N/A &ctx->request->server);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A
2N/A code = krb5_timeofday(context, &ctx->request_time);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A
2N/A code = krb5int_fast_as_armor(context, ctx->fast_state,
2N/A ctx->opte, ctx->request);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A if (krb5int_upgrade_to_fast_p(context, ctx->fast_state, padata)) {
2N/A code = krb5int_fast_as_armor(context, ctx->fast_state,
2N/A ctx->opte, ctx->request);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A }
2N/A /* give the preauth plugins a chance to prep the request body */
2N/A krb5_preauth_prepare_request(context, ctx->opte, ctx->request);
2N/A
2N/A ctx->request->from = krb5int_addint32(ctx->request_time,
2N/A ctx->start_time);
2N/A ctx->request->till = krb5int_addint32(ctx->request->from,
2N/A ctx->tkt_life);
2N/A
2N/A if (ctx->renew_life > 0) {
2N/A ctx->request->rtime =
2N/A krb5int_addint32(ctx->request->from, ctx->renew_life);
2N/A if (ctx->request->rtime < ctx->request->till) {
2N/A /* don't ask for a smaller renewable time than the lifetime */
2N/A ctx->request->rtime = ctx->request->till;
2N/A }
2N/A ctx->request->kdc_options &= ~(KDC_OPT_RENEWABLE_OK);
2N/A } else
2N/A ctx->request->rtime = 0;
2N/A code = krb5int_fast_prep_req_body(context, ctx->fast_state,
2N/A ctx->request,
2N/A &ctx->encoded_request_body);
2N/A if (code != 0)
2N/A goto cleanup;
2N/Acleanup:
2N/A return code;
2N/A}
2N/A
2N/Akrb5_error_code KRB5_CALLCONV
2N/Akrb5_init_creds_init(krb5_context context,
2N/A krb5_principal client,
2N/A krb5_prompter_fct prompter,
2N/A void *data,
2N/A krb5_deltat start_time,
2N/A krb5_get_init_creds_opt *options,
2N/A krb5_init_creds_context *pctx)
2N/A{
2N/A krb5_error_code code;
2N/A krb5_init_creds_context ctx;
2N/A int tmp;
2N/A char *str = NULL;
2N/A krb5_gic_opt_ext *opte;
2N/A
2N/A ctx = k5alloc(sizeof(*ctx), &code);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A
2N/A ctx->request = k5alloc(sizeof(krb5_kdc_req), &code);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A ctx->enc_pa_rep_permitted = 1;
2N/A code = krb5_copy_principal(context, client, &ctx->request->client);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A
2N/A ctx->prompter = prompter;
2N/A ctx->prompter_data = data;
2N/A ctx->gak_fct = krb5_get_as_key_password;
2N/A ctx->gak_data = &ctx->password;
2N/A
2N/A ctx->request_time = 0; /* filled in later */
2N/A ctx->start_time = start_time;
2N/A
2N/A if (options == NULL) {
2N/A code = krb5_get_init_creds_opt_alloc(context, &options);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A }
2N/A
2N/A code = krb5int_gic_opt_to_opte(context, options,
2N/A &ctx->opte, 1, "krb5_init_creds_init");
2N/A if (code != 0)
2N/A goto cleanup;
2N/A
2N/A opte = ctx->opte;
2N/A
2N/A ctx->get_data_rock.magic = CLIENT_ROCK_MAGIC;
2N/A ctx->get_data_rock.etype = &ctx->etype;
2N/A
2N/A /* Initialise request parameters as per krb5_get_init_creds() */
2N/A ctx->request->kdc_options = context->kdc_default_options;
2N/A
2N/A /* forwaradble */
2N/A if (opte->flags & KRB5_GET_INIT_CREDS_OPT_FORWARDABLE)
2N/A tmp = opte->forwardable;
2N/A else if (krb5int_libdefault_boolean(context, &ctx->request->client->realm,
2N/A KRB5_CONF_FORWARDABLE, &tmp) == 0)
2N/A ;
2N/A else
2N/A tmp = 0;
2N/A if (tmp)
2N/A ctx->request->kdc_options |= KDC_OPT_FORWARDABLE;
2N/A
2N/A /* proxiable */
2N/A if (opte->flags & KRB5_GET_INIT_CREDS_OPT_PROXIABLE)
2N/A tmp = opte->proxiable;
2N/A else if (krb5int_libdefault_boolean(context, &ctx->request->client->realm,
2N/A KRB5_CONF_PROXIABLE, &tmp) == 0)
2N/A ;
2N/A else
2N/A tmp = 0;
2N/A if (tmp)
2N/A ctx->request->kdc_options |= KDC_OPT_PROXIABLE;
2N/A
2N/A /* canonicalize */
2N/A if (opte->flags & KRB5_GET_INIT_CREDS_OPT_CANONICALIZE)
2N/A tmp = 1;
2N/A else if (krb5int_libdefault_boolean(context, &ctx->request->client->realm,
2N/A KRB5_CONF_CANONICALIZE, &tmp) == 0)
2N/A ;
2N/A else
2N/A tmp = 0;
2N/A if (tmp)
2N/A ctx->request->kdc_options |= KDC_OPT_CANONICALIZE;
2N/A
2N/A /* allow_postdate */
2N/A if (ctx->start_time > 0)
2N/A ctx->request->kdc_options |= KDC_OPT_ALLOW_POSTDATE | KDC_OPT_POSTDATED;
2N/A
2N/A /* ticket lifetime */
2N/A if (opte->flags & KRB5_GET_INIT_CREDS_OPT_TKT_LIFE)
2N/A ctx->tkt_life = options->tkt_life;
2N/A else if (krb5int_libdefault_string(context, &ctx->request->client->realm,
2N/A KRB5_CONF_TICKET_LIFETIME, &str) == 0) {
2N/A code = krb5_string_to_deltat(str, &ctx->tkt_life);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A free(str);
2N/A str = NULL;
2N/A } else
2N/A ctx->tkt_life = 24 * 60 * 60; /* previously hardcoded in kinit */
2N/A
2N/A /* renewable lifetime */
2N/A if (opte->flags & KRB5_GET_INIT_CREDS_OPT_RENEW_LIFE)
2N/A ctx->renew_life = options->renew_life;
2N/A else if (krb5int_libdefault_string(context, &ctx->request->client->realm,
2N/A KRB5_CONF_RENEW_LIFETIME, &str) == 0) {
2N/A code = krb5_string_to_deltat(str, &ctx->renew_life);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A free(str);
2N/A str = NULL;
2N/A } else
2N/A ctx->renew_life = 0;
2N/A
2N/A if (ctx->renew_life > 0)
2N/A ctx->request->kdc_options |= KDC_OPT_RENEWABLE;
2N/A
2N/A /* enctypes */
2N/A if (opte->flags & KRB5_GET_INIT_CREDS_OPT_ETYPE_LIST) {
2N/A ctx->request->ktype =
2N/A k5alloc((opte->etype_list_length * sizeof(krb5_enctype)),
2N/A &code);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A ctx->request->nktypes = opte->etype_list_length;
2N/A memcpy(ctx->request->ktype, opte->etype_list,
2N/A ctx->request->nktypes * sizeof(krb5_enctype));
2N/A } else if (krb5_get_default_in_tkt_ktypes(context,
2N/A &ctx->request->ktype) == 0) {
2N/A ctx->request->nktypes = krb5int_count_etypes(ctx->request->ktype);
2N/A } else {
2N/A /* there isn't any useful default here. */
2N/A code = KRB5_CONFIG_ETYPE_NOSUPP;
2N/A goto cleanup;
2N/A }
2N/A
2N/A /* addresess */
2N/A if (opte->flags & KRB5_GET_INIT_CREDS_OPT_ADDRESS_LIST) {
2N/A code = krb5_copy_addresses(context, opte->address_list,
2N/A &ctx->request->addresses);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A } else if (krb5int_libdefault_boolean(context, &ctx->request->client->realm,
2N/A KRB5_CONF_NOADDRESSES, &tmp) != 0
2N/A || tmp) {
2N/A ctx->request->addresses = NULL;
2N/A } else {
2N/A code = krb5_os_localaddr(context, &ctx->request->addresses);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A }
2N/A
2N/A if (opte->flags & KRB5_GET_INIT_CREDS_OPT_SALT) {
2N/A code = krb5int_copy_data_contents(context, opte->salt, &ctx->salt);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A } else {
2N/A ctx->salt.length = SALT_TYPE_AFS_LENGTH;
2N/A ctx->salt.data = NULL;
2N/A }
2N/A
2N/A /* Anonymous. */
2N/A if(opte->flags & KRB5_GET_INIT_CREDS_OPT_ANONYMOUS) {
2N/A ctx->request->kdc_options |= KDC_OPT_REQUEST_ANONYMOUS;
2N/A /* Remap @REALM to WELLKNOWN/ANONYMOUS@REALM. */
2N/A if (client->length == 1 && client->data[0].length ==0) {
2N/A krb5_principal new_client;
2N/A code = krb5_build_principal_ext(context, &new_client,
2N/A client->realm.length,
2N/A client->realm.data,
2N/A strlen(KRB5_WELLKNOWN_NAMESTR),
2N/A KRB5_WELLKNOWN_NAMESTR,
2N/A strlen(KRB5_ANONYMOUS_PRINCSTR),
2N/A KRB5_ANONYMOUS_PRINCSTR,
2N/A 0);
2N/A if (code)
2N/A goto cleanup;
2N/A krb5_free_principal(context, ctx->request->client);
2N/A ctx->request->client = new_client;
2N/A krb5_princ_type(context, ctx->request->client) = KRB5_NT_WELLKNOWN;
2N/A }
2N/A }
2N/A /* We will also handle anonymous if the input principal is the anonymous
2N/A * principal. */
2N/A if (krb5_principal_compare_any_realm(context, ctx->request->client,
2N/A krb5_anonymous_principal())) {
2N/A ctx->request->kdc_options |= KDC_OPT_REQUEST_ANONYMOUS;
2N/A krb5_princ_type(context, ctx->request->client) = KRB5_NT_WELLKNOWN;
2N/A }
2N/A code = restart_init_creds_loop(context, ctx, NULL);
2N/A if (code) {
2N/A /* Solaris Kerberos */
2N/A krb5_preauth_request_context_fini(context);
2N/A goto cleanup;
2N/A }
2N/A
2N/A *pctx = ctx;
2N/A ctx = NULL;
2N/A
2N/Acleanup:
2N/A krb5_init_creds_free(context, ctx);
2N/A free(str);
2N/A
2N/A return code;
2N/A}
2N/A
2N/Akrb5_error_code KRB5_CALLCONV
2N/Akrb5_init_creds_set_service(krb5_context context,
2N/A krb5_init_creds_context ctx,
2N/A const char *service)
2N/A{
2N/A char *s;
2N/A
2N/A s = strdup(service);
2N/A if (s == NULL)
2N/A return ENOMEM;
2N/A
2N/A free(ctx->in_tkt_service);
2N/A ctx->in_tkt_service = s;
2N/A
2N/A krb5_preauth_request_context_fini(context);
2N/A return restart_init_creds_loop(context, ctx, NULL);
2N/A}
2N/A
2N/Astatic krb5_error_code
2N/Ainit_creds_validate_reply(krb5_context context,
2N/A krb5_init_creds_context ctx,
2N/A krb5_data *reply)
2N/A{
2N/A krb5_error_code code;
2N/A krb5_error *error = NULL;
2N/A krb5_kdc_rep *as_reply = NULL;
2N/A
2N/A krb5_free_error(context, ctx->err_reply);
2N/A ctx->err_reply = NULL;
2N/A
2N/A krb5_free_kdc_rep(context, ctx->reply);
2N/A ctx->reply = NULL;
2N/A
2N/A if (krb5_is_krb_error(reply)) {
2N/A code = decode_krb5_error(reply, &error);
2N/A if (code != 0)
2N/A return code;
2N/A
2N/A assert(error != NULL);
2N/A
2N/A if (error->error == KRB_ERR_RESPONSE_TOO_BIG) {
2N/A krb5_free_error(context, error);
2N/A return KRB5KRB_ERR_RESPONSE_TOO_BIG;
2N/A } else {
2N/A ctx->err_reply = error;
2N/A return 0;
2N/A }
2N/A }
2N/A
2N/A /*
2N/A * Check to make sure it isn't a V4 reply.
2N/A */
2N/A if (reply->length != 0 && !krb5_is_as_rep(reply)) {
2N/A/* these are in <kerberosIV/prot.h> as well but it isn't worth including. */
2N/A#define V4_KRB_PROT_VERSION 4
2N/A#define V4_AUTH_MSG_ERR_REPLY (5<<1)
2N/A /* check here for V4 reply */
2N/A unsigned int t_switch;
2N/A
2N/A /* From v4 g_in_tkt.c: This used to be
2N/A switch (pkt_msg_type(rpkt) & ~1) {
2N/A but SCO 3.2v4 cc compiled that incorrectly. */
2N/A t_switch = reply->data[1];
2N/A t_switch &= ~1;
2N/A
2N/A if (t_switch == V4_AUTH_MSG_ERR_REPLY
2N/A && reply->data[0] == V4_KRB_PROT_VERSION) {
2N/A code = KRB5KRB_AP_ERR_V4_REPLY;
2N/A } else {
2N/A code = KRB5KRB_AP_ERR_MSG_TYPE;
2N/A }
2N/A return code;
2N/A }
2N/A
2N/A /* It must be a KRB_AS_REP message, or an bad returned packet */
2N/A code = decode_krb5_as_rep(reply, &as_reply);
2N/A if (code != 0)
2N/A return code;
2N/A
2N/A if (as_reply->msg_type != KRB5_AS_REP) {
2N/A krb5_free_kdc_rep(context, as_reply);
2N/A return KRB5KRB_AP_ERR_MSG_TYPE;
2N/A }
2N/A
2N/A ctx->reply = as_reply;
2N/A
2N/A return 0;
2N/A}
2N/A
2N/Astatic krb5_error_code
2N/Ainit_creds_step_request(krb5_context context,
2N/A krb5_init_creds_context ctx,
2N/A krb5_data *out)
2N/A{
2N/A krb5_error_code code;
2N/A
2N/A if (ctx->loopcount >= MAX_IN_TKT_LOOPS) {
2N/A code = KRB5_GET_IN_TKT_LOOP;
2N/A /* Solaris Kerberos */
2N/A {
2N/A char *s_name = NULL;
2N/A char *c_name = NULL;
2N/A krb5_error_code serr, cerr;
2N/A serr = krb5_unparse_name(context, ctx->cred.server, &s_name);
2N/A cerr = krb5_unparse_name(context, ctx->cred.client, &c_name);
2N/A krb5_set_error_message(context, code,
2N/A dgettext(TEXT_DOMAIN,
2N/A "Looping detected getting initial "
2N/A "creds: '%s' requesting ticket "
2N/A "'%s'. Max loops is %d. Make sure "
2N/A "a KDC is available"),
2N/A cerr ? "unknown" : c_name,
2N/A serr ? "unknown" : s_name,
2N/A MAX_IN_TKT_LOOPS);
2N/A if (s_name)
2N/A krb5_free_unparsed_name(context, s_name);
2N/A if (c_name)
2N/A krb5_free_unparsed_name(context, c_name);
2N/A }
2N/A goto cleanup;
2N/A }
2N/A
2N/A if (ctx->err_reply == NULL) {
2N/A /* either our first attempt, or retrying after PREAUTH_NEEDED */
2N/A code = krb5_do_preauth(context,
2N/A ctx->request,
2N/A ctx->encoded_request_body,
2N/A ctx->encoded_previous_request,
2N/A ctx->preauth_to_use,
2N/A &ctx->request->padata,
2N/A &ctx->salt,
2N/A &ctx->s2kparams,
2N/A &ctx->etype,
2N/A &ctx->as_key,
2N/A ctx->prompter,
2N/A ctx->prompter_data,
2N/A ctx->gak_fct,
2N/A ctx->gak_data,
2N/A &ctx->get_data_rock,
2N/A ctx->opte);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A } else {
2N/A if (ctx->preauth_to_use != NULL) {
2N/A /*
2N/A * Retry after an error other than PREAUTH_NEEDED,
2N/A * using e-data to figure out what to change.
2N/A */
2N/A code = krb5_do_preauth_tryagain(context,
2N/A ctx->request,
2N/A ctx->encoded_request_body,
2N/A ctx->encoded_previous_request,
2N/A ctx->preauth_to_use,
2N/A &ctx->request->padata,
2N/A ctx->err_reply,
2N/A &ctx->salt,
2N/A &ctx->s2kparams,
2N/A &ctx->etype,
2N/A &ctx->as_key,
2N/A ctx->prompter,
2N/A ctx->prompter_data,
2N/A ctx->gak_fct,
2N/A ctx->gak_data,
2N/A &ctx->get_data_rock,
2N/A ctx->opte);
2N/A } else {
2N/A /* No preauth supplied, so can't query the plugins. */
2N/A code = KRB5KRB_ERR_GENERIC;
2N/A }
2N/A if (code != 0) {
2N/A /* couldn't come up with anything better */
2N/A code = ctx->err_reply->error + ERROR_TABLE_BASE_krb5;
2N/A goto cleanup;
2N/A }
2N/A }
2N/A
2N/A if (ctx->encoded_previous_request != NULL) {
2N/A krb5_free_data(context, ctx->encoded_previous_request);
2N/A ctx->encoded_previous_request = NULL;
2N/A }
2N/A if (ctx->request->padata)
2N/A ctx->sent_nontrivial_preauth = 1;
2N/A if (ctx->enc_pa_rep_permitted) {
2N/A code = request_enc_pa_rep(&ctx->request->padata);
2N/A }
2N/A if (code)
2N/A goto cleanup;
2N/A code = krb5int_fast_prep_req(context, ctx->fast_state,
2N/A ctx->request, ctx->encoded_request_body,
2N/A encode_krb5_as_req,
2N/A &ctx->encoded_previous_request);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A
2N/A code = krb5int_copy_data_contents(context,
2N/A ctx->encoded_previous_request,
2N/A out);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A
2N/Acleanup:
2N/A return code;
2N/A}
2N/A
2N/A/*
2N/A * The control flow is complicated. In order to switch from non-FAST mode to
2N/A * FAST mode, we need to reset our pre-authentication state. FAST negotiation
2N/A * attempts to make sure we rarely have to do this. When FAST negotiation is
2N/A * working, we record whether FAST is available when we obtain an armor ticket;
2N/A * if so, we start out with FAST enabled . There are two complicated
2N/A * situations.
2N/A *
2N/A * First, if we get a PREAUTH_REQUIRED error including PADATA_FX_FAST back from
2N/A * a KDC in a case where we were not expecting to use FAST, and we have an
2N/A * armor ticket available, then we want to use FAST. That involves clearing
2N/A * out the pre-auth state, reinitializing the plugins and trying again with an
2N/A * armor key.
2N/A *
2N/A * Secondly, using the negotiation can cause problems with some older KDCs.
2N/A * Negotiation involves including a special padata item. Some KDCs, including
2N/A * MIT prior to 1.7, will return PREAUTH_FAILED rather than PREAUTH_REQUIRED in
2N/A * pre-authentication is required and unknown padata are included in the
2N/A * request. To make matters worse, these KDCs typically do not include a list
2N/A * of padata in PREAUTH_FAILED errors. So, if we get PREAUTH_FAILED and we
2N/A * generated no pre-authentication other than the negotiation then we want to
2N/A * retry without negotiation. In this case it is probably also desirable to
2N/A * retry with the preauth plugin state cleared.
2N/A *
2N/A * In all these cases we should not start over more than once. Control flow is
2N/A * managed by several variables.
2N/A *
2N/A * sent_nontrivial_preauth: if true, we sent preauth other than negotiation;
2N/A * no restart on PREAUTH_FAILED
2N/A *
2N/A * KRB5INT_FAST_ARMOR_AVAIL: fast_state_flag if desired we could generate
2N/A * armor; if not set, then we can't use FAST even if the KDC wants to.
2N/A *
2N/A * have_restarted: true if we've already restarted
2N/A */
2N/Astatic krb5_boolean
2N/Anegotiation_requests_restart(krb5_context context, krb5_init_creds_context ctx,
2N/A krb5_pa_data **padata)
2N/A{
2N/A if (!ctx->have_restarted &&
2N/A (krb5int_upgrade_to_fast_p(context, ctx->fast_state, padata) ||
2N/A (ctx->err_reply->error == KDC_ERR_PREAUTH_FAILED &&
2N/A !ctx->sent_nontrivial_preauth)))
2N/A return 1;
2N/A return 0;
2N/A}
2N/A
2N/A/* Ensure that the reply enctype was among the requested enctypes. */
2N/Astatic krb5_error_code
2N/Acheck_reply_enctype(krb5_init_creds_context ctx)
2N/A{
2N/A int i;
2N/A
2N/A for (i = 0; i < ctx->request->nktypes; i++) {
2N/A if (ctx->request->ktype[i] == ctx->reply->enc_part.enctype)
2N/A return 0;
2N/A }
2N/A return KRB5_CONFIG_ETYPE_NOSUPP;
2N/A}
2N/A
2N/A/*
2N/A * Solaris Kerberos
2N/A * Return 1 if any char in string is lower-case.
2N/A */
2N/Astatic int
2N/Ais_lower_case(char *s)
2N/A{
2N/A if (!s)
2N/A return 0;
2N/A
2N/A while (*s) {
2N/A if (islower((int)*s))
2N/A return 1;
2N/A s++;
2N/A }
2N/A return 0;
2N/A}
2N/A
2N/Astatic krb5_error_code
2N/Ainit_creds_step_reply(krb5_context context,
2N/A krb5_init_creds_context ctx,
2N/A krb5_data *in,
2N/A char *hostname_used)
2N/A{
2N/A krb5_error_code code;
2N/A krb5_pa_data **padata = NULL;
2N/A krb5_pa_data **kdc_padata = NULL;
2N/A krb5_boolean retry = FALSE;
2N/A int canon_flag = 0;
2N/A krb5_keyblock *strengthen_key = NULL;
2N/A krb5_keyblock encrypting_key;
2N/A krb5_boolean fast_avail;
2N/A
2N/A#if 0 /* ************ Begin IFDEF'ed OUT ***************************** */
2N/A encrypting_key.length = 0;
2N/A encrypting_key.contents = NULL;
2N/A#else
2N/A /* Solaris Kerberos: our keyblock has more fields */
2N/A (void) memset(&encrypting_key, 0, sizeof (encrypting_key));
2N/A#endif /* ************** END IFDEF'ed OUT ***************************** */
2N/A
2N/A /* process previous KDC response */
2N/A code = init_creds_validate_reply(context, ctx, in);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A
2N/A /* per referrals draft, enterprise principals imply canonicalization */
2N/A canon_flag = ((ctx->request->kdc_options & KDC_OPT_CANONICALIZE) != 0) ||
2N/A ctx->request->client->type == KRB5_NT_ENTERPRISE_PRINCIPAL;
2N/A
2N/A if (ctx->err_reply != NULL) {
2N/A code = krb5int_fast_process_error(context, ctx->fast_state,
2N/A &ctx->err_reply, &padata, &retry);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A if (negotiation_requests_restart(context, ctx, padata)) {
2N/A ctx->have_restarted = 1;
2N/A krb5_preauth_request_context_fini(context);
2N/A if ((ctx->fast_state->fast_state_flags & KRB5INT_FAST_DO_FAST) ==0)
2N/A ctx->enc_pa_rep_permitted = 0;
2N/A code = restart_init_creds_loop(context, ctx, padata);
2N/A krb5_free_error(context, ctx->err_reply);
2N/A ctx->err_reply = NULL;
2N/A } else if (ctx->err_reply->error == KDC_ERR_PREAUTH_REQUIRED &&
2N/A retry) {
2N/A /* reset the list of preauth types to try */
2N/A krb5_free_pa_data(context, ctx->preauth_to_use);
2N/A ctx->preauth_to_use = padata;
2N/A padata = NULL;
2N/A /* this will trigger a new call to krb5_do_preauth() */
2N/A krb5_free_error(context, ctx->err_reply);
2N/A ctx->err_reply = NULL;
2N/A code = sort_krb5_padata_sequence(context,
2N/A &ctx->request->client->realm,
2N/A ctx->preauth_to_use);
2N/A
2N/A } else if (canon_flag && ctx->err_reply->error == KDC_ERR_WRONG_REALM) {
2N/A if (ctx->err_reply->client == NULL ||
2N/A !krb5_princ_realm(context, ctx->err_reply->client)->length) {
2N/A code = KRB5KDC_ERR_WRONG_REALM;
2N/A goto cleanup;
2N/A }
2N/A /* Rewrite request.client with realm from error reply */
2N/A krb5_free_data_contents(context, &ctx->request->client->realm);
2N/A code = krb5int_copy_data_contents(context,
2N/A &ctx->err_reply->client->realm,
2N/A &ctx->request->client->realm);
2N/A /* this will trigger a new call to krb5_do_preauth() */
2N/A krb5_free_error(context, ctx->err_reply);
2N/A ctx->err_reply = NULL;
2N/A krb5_preauth_request_context_fini(context);
2N/A /* Permit another negotiation based restart. */
2N/A ctx->have_restarted = 0;
2N/A ctx->sent_nontrivial_preauth = 0;
2N/A code = restart_init_creds_loop(context, ctx, NULL);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A } else {
2N/A if (retry) {
2N/A code = 0;
2N/A } else {
2N/A /* error + no hints = give up */
2N/A code = (krb5_error_code)ctx->err_reply->error +
2N/A ERROR_TABLE_BASE_krb5;
2N/A }
2N/A }
2N/A
2N/A /* Return error code, or continue with next iteration */
2N/A goto cleanup;
2N/A }
2N/A
2N/A /* We have a response. Process it. */
2N/A assert(ctx->reply != NULL);
2N/A
2N/A /* Check for replies (likely forged) with unasked-for enctypes. */
2N/A code = check_reply_enctype(ctx);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A
2N/A /* process any preauth data in the as_reply */
2N/A krb5_clear_preauth_context_use_counts(context);
2N/A code = krb5int_fast_process_response(context, ctx->fast_state,
2N/A ctx->reply, &strengthen_key);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A
2N/A code = sort_krb5_padata_sequence(context, &ctx->request->client->realm,
2N/A ctx->reply->padata);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A
2N/A ctx->etype = ctx->reply->enc_part.enctype;
2N/A
2N/A code = krb5_do_preauth(context,
2N/A ctx->request,
2N/A ctx->encoded_request_body,
2N/A ctx->encoded_previous_request,
2N/A ctx->reply->padata,
2N/A &kdc_padata,
2N/A &ctx->salt,
2N/A &ctx->s2kparams,
2N/A &ctx->etype,
2N/A &ctx->as_key,
2N/A ctx->prompter,
2N/A ctx->prompter_data,
2N/A ctx->gak_fct,
2N/A ctx->gak_data,
2N/A &ctx->get_data_rock,
2N/A ctx->opte);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A
2N/A /*
2N/A * If we haven't gotten a salt from another source yet, set up one
2N/A * corresponding to the client principal returned by the KDC. We
2N/A * could get the same effect by passing local_as_reply->client to
2N/A * gak_fct below, but that would put the canonicalized client name
2N/A * in the prompt, which raises issues of needing to sanitize
2N/A * unprintable characters. So for now we just let it affect the
2N/A * salt. local_as_reply->client will be checked later on in
2N/A * verify_as_reply.
2N/A */
2N/A if (ctx->salt.length == SALT_TYPE_AFS_LENGTH && ctx->salt.data == NULL) {
2N/A code = krb5_principal2salt(context, ctx->reply->client, &ctx->salt);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A }
2N/A
2N/A /* XXX For 1.1.1 and prior KDC's, when SAM is used w/ USE_SAD_AS_KEY,
2N/A the AS_REP comes back encrypted in the user's longterm key
2N/A instead of in the SAD. If there was a SAM preauth, there
2N/A will be an as_key here which will be the SAD. If that fails,
2N/A use the gak_fct to get the password, and try again. */
2N/A
2N/A /* XXX because etypes are handled poorly (particularly wrt SAM,
2N/A where the etype is fixed by the kdc), we may want to try
2N/A decrypt_as_reply twice. If there's an as_key available, try
2N/A it. If decrypting the as_rep fails, or if there isn't an
2N/A as_key at all yet, then use the gak_fct to get one, and try
2N/A again. */
2N/A if (ctx->as_key.length) {
2N/A code = krb5int_fast_reply_key(context, strengthen_key, &ctx->as_key,
2N/A &encrypting_key);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A code = decrypt_as_reply(context, NULL, ctx->reply, NULL, NULL,
2N/A &encrypting_key, krb5_kdc_rep_decrypt_proc,
2N/A NULL);
2N/A } else
2N/A code = -1;
2N/A
2N/A if (code != 0) {
2N/A /* if we haven't get gotten a key, get it now */
2N/A code = (*ctx->gak_fct)(context, ctx->request->client,
2N/A ctx->reply->enc_part.enctype,
2N/A ctx->prompter, ctx->prompter_data,
2N/A &ctx->salt, &ctx->s2kparams,
2N/A &ctx->as_key, ctx->gak_data);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A
2N/A code = krb5int_fast_reply_key(context, strengthen_key, &ctx->as_key,
2N/A &encrypting_key);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A
2N/A code = decrypt_as_reply(context, NULL, ctx->reply, NULL, NULL,
2N/A &encrypting_key, krb5_kdc_rep_decrypt_proc,
2N/A NULL);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A }
2N/A
2N/A code = krb5int_fast_verify_nego(context, ctx->fast_state,
2N/A ctx->reply, ctx->encoded_previous_request,
2N/A &encrypting_key, &fast_avail);
2N/A if (code)
2N/A goto cleanup;
2N/A code = verify_as_reply(context, ctx->request_time,
2N/A ctx->request, ctx->reply);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A code = verify_anonymous(context, ctx->request, ctx->reply,
2N/A &encrypting_key);
2N/A if (code)
2N/A goto cleanup;
2N/A
2N/A code = stash_as_reply(context, ctx->request_time, ctx->request,
2N/A ctx->reply, &ctx->cred, NULL);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A if (ctx->opte && ctx->opte->opt_private->out_ccache) {
2N/A krb5_ccache out_ccache = ctx->opte->opt_private->out_ccache;
2N/A krb5_data config_data;
2N/A code = krb5_cc_initialize(context, out_ccache, ctx->cred.client);
2N/A if (code != 0)
2N/A goto cc_cleanup;
2N/A code = krb5_cc_store_cred(context, out_ccache, &ctx->cred);
2N/A if (code != 0)
2N/A goto cc_cleanup;
2N/A if (fast_avail) {
2N/A config_data.data = "yes";
2N/A config_data.length = strlen(config_data.data);
2N/A code = krb5_cc_set_config(context, out_ccache, ctx->cred.server,
2N/A KRB5_CONF_FAST_AVAIL, &config_data);
2N/A }
2N/A cc_cleanup:
2N/A if (code !=0) {
2N/A const char *msg;
2N/A msg = krb5_get_error_message(context, code);
2N/A krb5_set_error_message(context, code,
2N/A "%s while storing credentials", msg);
2N/A krb5_free_error_message(context, msg);
2N/A /* Solaris Kerberos */
2N/A goto cleanup;
2N/A }
2N/A }
2N/A
2N/A krb5_preauth_request_context_fini(context);
2N/A
2N/A /* success */
2N/A code = 0;
2N/A ctx->flags |= KRB5_INIT_CREDS_STEP_FLAG_COMPLETE;
2N/A
2N/Acleanup:
2N/A /* Solaris Kerberos dtrace */
2N/A if (krb5_is_as_rep(in)) {
2N/A k5_trace_kdc_rep_read(in, code == 0 ? ctx->reply : NULL);
2N/A }
2N/A
2N/A /* Solaris Kerberos: begin spruce-up the err msg */
2N/A if (code != 0) {
2N/A char *client_name = NULL;
2N/A /* See if we can produce a more detailed error message. */
2N/A switch (code) {
2N/A case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
2N/A if (krb5_unparse_name(context, ctx->request->client, &client_name) == 0) {
2N/A krb5_set_error_message(context, code,
2N/A dgettext(TEXT_DOMAIN,
2N/A "Client '%s' not found in Kerberos database"),
2N/A client_name);
2N/A krb5_free_unparsed_name(context, client_name);
2N/A }
2N/A break;
2N/A /* Solaris Kerberos: spruce-up the err msg */
2N/A case KRB5_PREAUTH_FAILED:
2N/A case KRB5KDC_ERR_PREAUTH_FAILED:
2N/A if (krb5_unparse_name(context, ctx->request->client, &client_name) == 0) {
2N/A krb5_set_error_message(context, code,
2N/A dgettext(TEXT_DOMAIN,
2N/A "Client '%s' pre-authentication failed"),
2N/A client_name);
2N/A krb5_free_unparsed_name(context, client_name);
2N/A }
2N/A break;
2N/A /* Solaris Kerberos: spruce-up the err msg */
2N/A case KRB5KRB_AP_ERR_SKEW: /* KRB_AP_ERR_SKEW + ERROR_TABLE_BASE_krb5 */
2N/A {
2N/A char *s_name = NULL;
2N/A char *c_name = NULL;
2N/A char stimestring[17];
2N/A char fill = ' ';
2N/A krb5_error_code c_err, s_err, s_time = 0;
2N/A krb5_timestamp time_now;
2N/A
2N/A s_err = krb5_unparse_name(context,
2N/A ctx->request->server, &s_name);
2N/A if (ctx->err_reply) {
2N/A s_time = krb5_timestamp_to_sfstring(ctx->err_reply->stime,
2N/A stimestring,
2N/A sizeof (stimestring),
2N/A &fill);
2N/A }
2N/A (void) krb5_timeofday(context, &time_now);
2N/A c_err = krb5_unparse_name(context, ctx->request->client, &c_name);
2N/A krb5_set_error_message(context, code,
2N/A dgettext(TEXT_DOMAIN,
2N/A "Clock skew too great: '%s' requesting ticket '%s' from KDC '%s' (%s). Skew is %dm"),
2N/A c_err == 0 ? c_name : "unknown",
2N/A s_err == 0 ? s_name : "unknown",
2N/A hostname_used ? hostname_used : "unknown",
2N/A s_time == 0 ? stimestring : "unknown",
2N/A (s_time != 0) ? 0 :
2N/A (abs(ctx->err_reply->stime - time_now) / 60));
2N/A if (s_name)
2N/A krb5_free_unparsed_name(context, s_name);
2N/A if (c_name)
2N/A krb5_free_unparsed_name(context, c_name);
2N/A }
2N/A break;
2N/A case KRB5_KDCREP_MODIFIED:
2N/A if (krb5_unparse_name(context, ctx->request->client, &client_name) == 0) {
2N/A /*
2N/A * Solaris Kerberos
2N/A * Extra err msg for common(?) case of
2N/A * 'kinit user@lower-case-def-realm'.
2N/A * DNS SRV recs will match (case insensitive) and trigger sendto
2N/A * KDC and result in this error (at least w/MSFT AD KDC).
2N/A */
2N/A char *realm = strpbrk(client_name, "@");
2N/A int set = 0;
2N/A if (realm++) {
2N/A if (realm && realm[0] && is_lower_case(realm)) {
2N/A krb5_set_error_message(context, code,
2N/A dgettext(TEXT_DOMAIN,
2N/A "KDC reply did not match expectations for client '%s': lower-case detected in realm '%s'"),
2N/A client_name, realm);
2N/A set = 1;
2N/A }
2N/A }
2N/A if (!set)
2N/A krb5_set_error_message(context, code,
2N/A dgettext(TEXT_DOMAIN,
2N/A "KDC reply did not match expectations for client '%s'"),
2N/A client_name);
2N/A krb5_free_unparsed_name(context, client_name);
2N/A }
2N/A break;
2N/A default:
2N/A break;
2N/A }
2N/A }
2N/A /* Solaris Kerberos: end spruce-up the err msg */
2N/A krb5_free_pa_data(context, padata);
2N/A krb5_free_pa_data(context, kdc_padata);
2N/A krb5_free_keyblock(context, strengthen_key);
2N/A krb5_free_keyblock_contents(context, &encrypting_key);
2N/A
2N/A return code;
2N/A}
2N/A
2N/A/*
2N/A * Do next step of credentials acquisition.
2N/A *
2N/A * On success returns 0 or KRB5KRB_ERR_RESPONSE_TOO_BIG if the request
2N/A * should be sent with TCP.
2N/A */
2N/Akrb5_error_code KRB5_CALLCONV
2N/Akrb5_init_creds_step(krb5_context context,
2N/A krb5_init_creds_context ctx,
2N/A krb5_data *in,
2N/A krb5_data *out,
2N/A krb5_data *realm,
2N/A unsigned int *flags,
2N/A char *hostname_used) /* Solaris Kerberos better errors */
2N/A{
2N/A krb5_error_code code = 0, code2;
2N/A
2N/A *flags = 0;
2N/A
2N/A out->data = NULL;
2N/A out->length = 0;
2N/A
2N/A realm->data = NULL;
2N/A realm->length = 0;
2N/A
2N/A if (ctx->flags & KRB5_INIT_CREDS_STEP_FLAG_COMPLETE)
2N/A goto cleanup;
2N/A
2N/A if (in->length != 0) {
2N/A code = init_creds_step_reply(context, ctx, in, hostname_used);
2N/A if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG) {
2N/A code2 = krb5int_copy_data_contents(context,
2N/A ctx->encoded_previous_request,
2N/A out);
2N/A if (code2 != 0) {
2N/A code = code2;
2N/A goto cleanup;
2N/A }
2N/A goto copy_realm;
2N/A }
2N/A if (code != 0 || (ctx->flags & KRB5_INIT_CREDS_STEP_FLAG_COMPLETE))
2N/A goto cleanup;
2N/A }
2N/A
2N/A code = init_creds_step_request(context, ctx, out);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A
2N/A /* Only a new request increments the loop count, not a TCP retry */
2N/A ctx->loopcount++;
2N/A
2N/Acopy_realm:
2N/A assert(ctx->request->server != NULL);
2N/A
2N/A code2 = krb5int_copy_data_contents(context,
2N/A &ctx->request->server->realm,
2N/A realm);
2N/A if (code2 != 0) {
2N/A code = code2;
2N/A goto cleanup;
2N/A }
2N/A
2N/Acleanup:
2N/A *flags = (ctx->flags & KRB5_INIT_CREDS_STEP_FLAG_COMPLETE) ? 0 : 1;
2N/A return code;
2N/A}
2N/A
2N/Akrb5_error_code KRB5_CALLCONV
2N/Akrb5int_get_init_creds(krb5_context context,
2N/A krb5_creds *creds,
2N/A krb5_principal client,
2N/A krb5_prompter_fct prompter,
2N/A void *prompter_data,
2N/A krb5_deltat start_time,
2N/A char *in_tkt_service,
2N/A krb5_get_init_creds_opt *options,
2N/A krb5_gic_get_as_key_fct gak_fct,
2N/A void *gak_data,
2N/A int *use_master,
2N/A krb5_kdc_rep **as_reply)
2N/A{
2N/A krb5_error_code code;
2N/A krb5_init_creds_context ctx = NULL;
2N/A
2N/A code = krb5_init_creds_init(context,
2N/A client,
2N/A prompter,
2N/A prompter_data,
2N/A start_time,
2N/A options,
2N/A &ctx);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A
2N/A ctx->gak_fct = gak_fct;
2N/A ctx->gak_data = gak_data;
2N/A
2N/A if (in_tkt_service) {
2N/A code = krb5_init_creds_set_service(context, ctx, in_tkt_service);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A }
2N/A
2N/A code = init_creds_get(context, ctx, use_master);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A
2N/A code = krb5_init_creds_get_creds(context, ctx, creds);
2N/A if (code != 0)
2N/A goto cleanup;
2N/A
2N/A if (as_reply != NULL) {
2N/A *as_reply = ctx->reply;
2N/A ctx->reply = NULL;
2N/A }
2N/A
2N/Acleanup:
2N/A krb5_init_creds_free(context, ctx);
2N/A
2N/A return code;
2N/A}
2N/A
2N/Akrb5_error_code
2N/Akrb5int_populate_gic_opt(krb5_context context, krb5_get_init_creds_opt **out,
2N/A krb5_flags options, krb5_address *const *addrs,
2N/A krb5_enctype *ktypes,
2N/A krb5_preauthtype *pre_auth_types, krb5_creds *creds)
2N/A{
2N/A int i;
2N/A krb5_int32 starttime;
2N/A krb5_get_init_creds_opt *opt;
2N/A krb5_error_code retval;
2N/A
2N/A *out = NULL;
2N/A retval = krb5_get_init_creds_opt_alloc(context, &opt);
2N/A if (retval)
2N/A return(retval);
2N/A
2N/A if (addrs)
2N/A krb5_get_init_creds_opt_set_address_list(opt, (krb5_address **) addrs);
2N/A if (ktypes) {
2N/A i = krb5int_count_etypes(ktypes);
2N/A if (i)
2N/A krb5_get_init_creds_opt_set_etype_list(opt, ktypes, i);
2N/A }
2N/A if (pre_auth_types) {
2N/A for (i=0; pre_auth_types[i]; i++);
2N/A if (i)
2N/A krb5_get_init_creds_opt_set_preauth_list(opt, pre_auth_types, i);
2N/A }
2N/A if (options&KDC_OPT_FORWARDABLE)
2N/A krb5_get_init_creds_opt_set_forwardable(opt, 1);
2N/A else krb5_get_init_creds_opt_set_forwardable(opt, 0);
2N/A if (options&KDC_OPT_PROXIABLE)
2N/A krb5_get_init_creds_opt_set_proxiable(opt, 1);
2N/A else krb5_get_init_creds_opt_set_proxiable(opt, 0);
2N/A if (creds && creds->times.endtime) {
2N/A retval = krb5_timeofday(context, &starttime);
2N/A if (retval)
2N/A goto cleanup;
2N/A if (creds->times.starttime) starttime = creds->times.starttime;
2N/A krb5_get_init_creds_opt_set_tkt_life(opt, creds->times.endtime - starttime);
2N/A }
2N/A *out = opt;
2N/A return 0;
2N/A
2N/Acleanup:
2N/A krb5_get_init_creds_opt_free(context, opt);
2N/A return retval;
2N/A}