kdc_process_tgs_req(kdc_realm_t *kdc_active_realm,
                    krb5_kdc_req *request, const krb5_fulladdr *from,
                    krb5_data *pkt, krb5_ticket **ticket_out,
                    krb5_db_entry **krbtgt_ptr,
                    krb5_keyblock **tgskey,
                    krb5_keyblock **subkey,
                    krb5_pa_data **pa_tgs_req)
{
    krb5_pa_data        * tmppa;
    krb5_ap_req         * apreq;
    krb5_error_code       retval;
    krb5_authdata **authdata = NULL;
    krb5_data             scratch1;
    krb5_data           * scratch = NULL;
    krb5_boolean          foreign_server = FALSE;
    krb5_auth_context     auth_context = NULL;
    krb5_authenticator  * authenticator = NULL;
    krb5_checksum       * his_cksum = NULL;
    krb5_db_entry       * krbtgt = NULL;
    krb5_ticket         * ticket;

    *ticket_out = NULL;
    *krbtgt_ptr = NULL;
    *tgskey = NULL;

    tmppa = krb5int_find_pa_data(kdc_context,
                                 request->padata, KRB5_PADATA_AP_REQ);
    if (!tmppa)
        return KRB5KDC_ERR_PADATA_TYPE_NOSUPP;

    scratch1.length = tmppa->length;
    scratch1.data = (char *)tmppa->contents;
    if ((retval = decode_krb5_ap_req(&scratch1, &apreq)))
        return retval;
    ticket = apreq->ticket;

    if (isflagset(apreq->ap_options, AP_OPTS_USE_SESSION_KEY) ||
        isflagset(apreq->ap_options, AP_OPTS_MUTUAL_REQUIRED)) {
        krb5_klog_syslog(LOG_INFO, _("TGS_REQ: SESSION KEY or MUTUAL"));
        retval = KRB5KDC_ERR_POLICY;
        goto cleanup;
    }

    /* If the "server" principal in the ticket is not something
       in the local realm, then we must refuse to service the request
       if the client claims to be from the local realm.

       If we don't do this, then some other realm's nasty KDC can
       claim to be authenticating a client from our realm, and we'll
       give out tickets concurring with it!

       we set a flag here for checking below.
    */
    foreign_server = !is_local_principal(kdc_active_realm,
                                         apreq->ticket->server);

    if ((retval = krb5_auth_con_init(kdc_context, &auth_context)))
        goto cleanup;

    /* Don't use a replay cache. */
    if ((retval = krb5_auth_con_setflags(kdc_context, auth_context, 0)))
        goto cleanup;

    if ((retval = krb5_auth_con_setaddrs(kdc_context, auth_context, NULL,
                                         from->address)) )
        goto cleanup_auth_context;

    retval = kdc_rd_ap_req(kdc_active_realm,
                           apreq, auth_context, &krbtgt, tgskey);
    if (retval)
        goto cleanup_auth_context;

    /* "invalid flag" tickets can must be used to validate */
    if (isflagset(ticket->enc_part2->flags, TKT_FLG_INVALID) &&
        !isflagset(request->kdc_options, KDC_OPT_VALIDATE)) {
        retval = KRB5KRB_AP_ERR_TKT_INVALID;
        goto cleanup_auth_context;
    }

    if ((retval = krb5_auth_con_getrecvsubkey(kdc_context,
                                              auth_context, subkey)))
        goto cleanup_auth_context;

    if ((retval = krb5_auth_con_getauthenticator(kdc_context, auth_context,
                                                 &authenticator)))
        goto cleanup_auth_context;

    retval = krb5_find_authdata(kdc_context,
                                ticket->enc_part2->authorization_data,
                                authenticator->authorization_data,
                                KRB5_AUTHDATA_FX_ARMOR, &authdata);
    if (retval != 0)
        goto cleanup_authenticator;
    if (authdata&& authdata[0]) {
        k5_setmsg(kdc_context, KRB5KDC_ERR_POLICY,
                  "ticket valid only as FAST armor");
        retval = KRB5KDC_ERR_POLICY;
        krb5_free_authdata(kdc_context, authdata);
        goto cleanup_authenticator;
    }
    krb5_free_authdata(kdc_context, authdata);


    /* Check for a checksum */
    if (!(his_cksum = authenticator->checksum)) {
        retval = KRB5KRB_AP_ERR_INAPP_CKSUM;
        goto cleanup_authenticator;
    }

    /* make sure the client is of proper lineage (see above) */
    if (foreign_server &&
        !krb5int_find_pa_data(kdc_context,
                              request->padata, KRB5_PADATA_FOR_USER)) {
        if (is_local_principal(kdc_active_realm,
                               ticket->enc_part2->client)) {
            /* someone in a foreign realm claiming to be local */
            krb5_klog_syslog(LOG_INFO, _("PROCESS_TGS: failed lineage check"));
            retval = KRB5KDC_ERR_POLICY;
            goto cleanup_authenticator;
        }
    }

    /*
     * Check application checksum vs. tgs request
     *
     * We try checksumming the req-body two different ways: first we
     * try reaching into the raw asn.1 stream (if available), and
     * checksum that directly; if that fails, then we try encoding
     * using our local asn.1 library.
     */
    if (pkt && (fetch_asn1_field((unsigned char *) pkt->data,
                                 1, 4, &scratch1) >= 0)) {
        if (comp_cksum(kdc_context, &scratch1, ticket, his_cksum)) {
            if (!(retval = encode_krb5_kdc_req_body(request, &scratch)))
                retval = comp_cksum(kdc_context, scratch, ticket, his_cksum);
            krb5_free_data(kdc_context, scratch);
            if (retval)
                goto cleanup_authenticator;
        }
    }

    *pa_tgs_req = tmppa;
    *krbtgt_ptr = krbtgt;
    krbtgt = NULL;

cleanup_authenticator:
    krb5_free_authenticator(kdc_context, authenticator);

cleanup_auth_context:
    krb5_auth_con_free(kdc_context, auth_context);

cleanup:
    if (retval != 0) {
        krb5_free_keyblock(kdc_context, *tgskey);
        *tgskey = NULL;
    }
    if (apreq->ticket->enc_part2 != NULL) {
        /* Steal the decrypted ticket pointer, even on error. */
        *ticket_out = apreq->ticket;
        apreq->ticket = NULL;
    }
    krb5_free_ap_req(kdc_context, apreq);
    krb5_db_free_principal(kdc_context, krbtgt);
    return retval;
}