#include "k5-int.h"
#include "kdc_util.h"
#include "extern.h"
#include <stdio.h>
#include <ctype.h>
#include <syslog.h>
#include "adm.h"
#include "adm_proto.h"
#include <limits.h>
#ifdef USE_RCACHE
static char *kdc_current_rcname = (char *) NULL;
krb5_deltat rc_lifetime;
#endif
#ifdef USE_RCACHE
krb5_error_code
kdc_initialize_rcache(krb5_context kcontext, char *rcache_name)
{
krb5_error_code retval;
char *rcname;
char *sname;
rcname = (rcache_name) ? rcache_name : kdc_current_rcname;
rc_lifetime = kcontext->clockskew;
if (!rcname)
rcname = KDCRCACHE;
if (!(retval = krb5_rc_resolve_full(kcontext, &kdc_rcache, rcname))) {
if (!(retval = krb5_rc_recover(kcontext, kdc_rcache)) ||
!(retval = krb5_rc_initialize(kcontext,
kdc_rcache,
kcontext->clockskew))
) {
if (!(retval = krb5_rc_expunge(kcontext, kdc_rcache))) {
sname = kdc_current_rcname;
kdc_current_rcname = strdup(rcname);
if (sname)
free(sname);
}
}
if (retval)
krb5_rc_close(kcontext, kdc_rcache);
}
return(retval);
}
#endif
krb5_error_code
concat_authorization_data(krb5_authdata **first, krb5_authdata **second,
krb5_authdata ***output)
{
register int i, j;
register krb5_authdata **ptr, **retdata;
i = 0;
if (first)
for (ptr = first; *ptr; ptr++)
i++;
if (second)
for (ptr = second; *ptr; ptr++)
i++;
retdata = (krb5_authdata **)malloc((i+1)*sizeof(*retdata));
if (!retdata)
return ENOMEM;
retdata[i] = 0;
for (i = 0, j = 0, ptr = first; j < 2 ; ptr = second, j++)
while (ptr && *ptr) {
retdata[i] = (krb5_authdata *)malloc(sizeof(*retdata[i]));
if (!retdata[i]) {
krb5_free_authdata(kdc_context, retdata);
return ENOMEM;
}
*retdata[i] = **ptr;
if (!(retdata[i]->contents =
(krb5_octet *)malloc(retdata[i]->length))) {
free((char *)retdata[i]);
retdata[i] = 0;
krb5_free_authdata(kdc_context, retdata);
return ENOMEM;
}
memcpy((char *) retdata[i]->contents,
(char *)(*ptr)->contents,
retdata[i]->length);
ptr++;
i++;
}
*output = retdata;
return 0;
}
krb5_boolean
realm_compare(krb5_principal princ1, krb5_principal princ2)
{
krb5_data *realm1 = krb5_princ_realm(kdc_context, princ1);
krb5_data *realm2 = krb5_princ_realm(kdc_context, princ2);
return((realm1->length == realm2->length) &&
!memcmp(realm1->data, realm2->data, realm1->length));
}
krb5_boolean krb5_is_tgs_principal(krb5_principal principal)
{
if ((krb5_princ_size(kdc_context, principal) > 0) &&
(krb5_princ_component(kdc_context, principal, 0)->length ==
KRB5_TGS_NAME_SIZE) &&
(!memcmp(krb5_princ_component(kdc_context, principal, 0)->data,
KRB5_TGS_NAME, KRB5_TGS_NAME_SIZE)))
return TRUE;
return FALSE;
}
static krb5_error_code
comp_cksum(krb5_context kcontext, krb5_data *source, krb5_ticket *ticket,
krb5_checksum *his_cksum)
{
krb5_error_code retval;
krb5_boolean valid;
if (!krb5_c_valid_cksumtype(his_cksum->checksum_type))
return KRB5KDC_ERR_SUMTYPE_NOSUPP;
if (!krb5_c_is_coll_proof_cksum(his_cksum->checksum_type))
return KRB5KRB_AP_ERR_INAPP_CKSUM;
if ((retval = krb5_c_verify_checksum(kcontext, ticket->enc_part2->session,
KRB5_KEYUSAGE_TGS_REQ_AUTH_CKSUM,
source, his_cksum, &valid)))
return(retval);
if (!valid)
return(KRB5KRB_AP_ERR_BAD_INTEGRITY);
return(0);
}
krb5_error_code
kdc_process_tgs_req(krb5_kdc_req *request, const krb5_fulladdr *from,
krb5_data *pkt, krb5_ticket **ticket,
krb5_keyblock **subkey)
{
krb5_pa_data ** tmppa;
krb5_ap_req * apreq;
krb5_error_code retval;
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;
if (!request->padata)
return KRB5KDC_ERR_PADATA_TYPE_NOSUPP;
for (tmppa = request->padata; *tmppa; tmppa++) {
if ((*tmppa)->pa_type == KRB5_PADATA_AP_REQ)
break;
}
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;
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 ((krb5_princ_realm(kdc_context, apreq->ticket->server)->length !=
krb5_princ_realm(kdc_context, tgs_server)->length) ||
memcmp(krb5_princ_realm(kdc_context, apreq->ticket->server)->data,
krb5_princ_realm(kdc_context, tgs_server)->data,
krb5_princ_realm(kdc_context, tgs_server)->length))
foreign_server = TRUE;
if ((retval = krb5_auth_con_init(kdc_context, &auth_context)))
goto cleanup;
if ((retval = krb5_auth_con_setaddrs(kdc_context, auth_context, NULL,
from->address)) )
goto cleanup_auth_context;
#ifdef USE_RCACHE
if ((retval = krb5_auth_con_setrcache(kdc_context, auth_context,
kdc_rcache)))
goto cleanup_auth_context;
#endif
if ((retval = krb5_rd_req_decoded_anyflag(kdc_context, &auth_context, apreq,
apreq->ticket->server,
kdc_active_realm->realm_keytab,
NULL, ticket))) {
#ifdef USE_RCACHE
if ((retval == KRB5_RC_IO_IO) ||
(retval == KRB5_RC_IO_UNKNOWN)) {
(void) krb5_rc_close(kdc_context, kdc_rcache);
kdc_rcache = (krb5_rcache) NULL;
if (!(retval = kdc_initialize_rcache(kdc_context, (char *) NULL))) {
if ((retval = krb5_auth_con_setrcache(kdc_context, auth_context,
kdc_rcache)) ||
(retval = krb5_rd_req_decoded_anyflag(kdc_context, &auth_context,
apreq, apreq->ticket->server,
kdc_active_realm->realm_keytab,
NULL, ticket))
)
goto cleanup_auth_context;
}
} else
goto cleanup_auth_context;
#else
goto cleanup_auth_context;
#endif
}
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;
if (!(his_cksum = authenticator->checksum)) {
retval = KRB5KRB_AP_ERR_INAPP_CKSUM;
goto cleanup_authenticator;
}
if (foreign_server) {
krb5_data *tkt_realm = krb5_princ_realm(kdc_context,
(*ticket)->enc_part2->client);
krb5_data *tgs_realm = krb5_princ_realm(kdc_context, tgs_server);
if (tkt_realm->length == tgs_realm->length &&
!memcmp(tkt_realm->data, tgs_realm->data, tgs_realm->length)) {
krb5_klog_syslog(LOG_INFO, "PROCESS_TGS: failed lineage check");
retval = KRB5KDC_ERR_POLICY;
goto cleanup_authenticator;
}
}
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);
}
}
cleanup_authenticator:
krb5_free_authenticator(kdc_context, authenticator);
cleanup_auth_context:
#ifdef USE_RCACHE
(void) krb5_auth_con_setrcache(kdc_context, auth_context, 0);
#endif
krb5_auth_con_free(kdc_context, auth_context);
cleanup:
krb5_free_ap_req(kdc_context, apreq);
return retval;
}
krb5_error_code
kdc_get_server_key(krb5_ticket *ticket, krb5_keyblock **key, krb5_kvno *kvno)
{
krb5_error_code retval;
krb5_db_entry server;
krb5_boolean more;
int nprincs;
krb5_key_data * server_key;
nprincs = 1;
if ((retval = krb5_db_get_principal(kdc_context, ticket->server,
&server, &nprincs,
&more))) {
return(retval);
}
if (more) {
krb5_db_free_principal(kdc_context, &server, nprincs);
return(KRB5KDC_ERR_PRINCIPAL_NOT_UNIQUE);
} else if (nprincs != 1) {
char *sname;
krb5_db_free_principal(kdc_context, &server, nprincs);
if (!krb5_unparse_name(kdc_context, ticket->server, &sname)) {
limit_string(sname);
krb5_klog_syslog(LOG_ERR,"TGS_REQ: UNKNOWN SERVER: server='%s'",
sname);
free(sname);
}
return(KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN);
}
retval = krb5_dbe_find_enctype(kdc_context, &server,
ticket->enc_part.enctype, -1,
ticket->enc_part.kvno, &server_key);
if (retval)
goto errout;
if (!server_key) {
retval = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
goto errout;
}
*kvno = server_key->key_data_kvno;
if ((*key = (krb5_keyblock *)malloc(sizeof **key))) {
retval = krb5_dbekd_decrypt_key_data(kdc_context, &master_keyblock,
server_key,
*key, NULL);
} else
retval = ENOMEM;
errout:
krb5_db_free_principal(kdc_context, &server, nprincs);
return retval;
}
static krb5_last_req_entry nolrentry = { KV5M_LAST_REQ_ENTRY, KRB5_LRQ_NONE, 0 };
static krb5_last_req_entry *nolrarray[] = { &nolrentry, 0 };
krb5_error_code
fetch_last_req_info(krb5_db_entry *dbentry, krb5_last_req_entry ***lrentry)
{
*lrentry = nolrarray;
return 0;
}
krb5_error_code
check_hot_list(krb5_ticket *ticket)
{
return 0;
}
#define MAX_REALM_LN 500
static int
subrealm(char *r1, char *r2)
{
size_t l1,l2;
l1 = strlen(r1);
l2 = strlen(r2);
if(l2 <= l1) return(0);
if((*r1 == '/') && (*r2 == '/') && (strncmp(r1,r2,l1) == 0)) return(l1-l2);
if((*r1 != '/') && (*r2 != '/') && (strncmp(r1,r2+l2-l1,l1) == 0))
return(l2-l1);
return(0);
}
static char *
data2string (krb5_data *d)
{
char *s;
s = malloc(d->length + 1);
if (s) {
memcpy(s, d->data, d->length);
s[d->length] = 0;
}
return s;
}
krb5_error_code
add_to_transited(krb5_data *tgt_trans, krb5_data *new_trans,
krb5_principal tgs, krb5_principal client,
krb5_principal server)
{
krb5_error_code retval;
char *realm;
char *trans;
char *otrans, *otrans_ptr;
char prev[MAX_REALM_LN];
char next[MAX_REALM_LN];
char current[MAX_REALM_LN];
char exp[MAX_REALM_LN];
int i;
int clst, nlst;
int pl, pl1;
int added;
realm = data2string(krb5_princ_realm(kdc_context, tgs));
if (realm == NULL)
return(ENOMEM);
otrans = data2string(tgt_trans);
if (otrans == NULL) {
free(realm);
return(ENOMEM);
}
otrans_ptr = otrans;
if (!(trans = (char *) malloc(strlen(realm) + strlen(otrans) + 3))) {
retval = ENOMEM;
goto fail;
}
if (new_trans->data) free(new_trans->data);
new_trans->data = trans;
new_trans->length = 0;
trans[0] = '\0';
prev[0] = '\0';
for (i = 0; *otrans != '\0';) {
if (*otrans == '\\') {
if (*(++otrans) == '\0')
break;
else
continue;
}
if (*otrans == ',') {
otrans++;
break;
}
current[i++] = *otrans++;
if (i >= MAX_REALM_LN) {
retval = KRB5KRB_AP_ERR_ILL_CR_TKT;
goto fail;
}
}
current[i] = '\0';
added = (krb5_princ_realm(kdc_context, client)->length == strlen(realm) &&
!strncmp(krb5_princ_realm(kdc_context, client)->data, realm, strlen(realm))) ||
(krb5_princ_realm(kdc_context, server)->length == strlen(realm) &&
!strncmp(krb5_princ_realm(kdc_context, server)->data, realm, strlen(realm)));
while (current[0]) {
clst = strlen(current) - 1;
if (current[0] == ' ') {
strncpy(exp, current+1, sizeof(exp) - 1);
exp[sizeof(exp) - 1] = '\0';
}
else if ((current[0] == '/') && (prev[0] == '/')) {
strncpy(exp, prev, sizeof(exp) - 1);
exp[sizeof(exp) - 1] = '\0';
if (strlen(exp) + strlen(current) + 1 >= MAX_REALM_LN) {
retval = KRB5KRB_AP_ERR_ILL_CR_TKT;
goto fail;
}
strncat(exp, current, sizeof(exp) - 1 - strlen(exp));
}
else if (current[clst] == '.') {
strncpy(exp, current, sizeof(exp) - 1);
exp[sizeof(exp) - 1] = '\0';
if (strlen(exp) + strlen(prev) + 1 >= MAX_REALM_LN) {
retval = KRB5KRB_AP_ERR_ILL_CR_TKT;
goto fail;
}
strncat(exp, prev, sizeof(exp) - 1 - strlen(exp));
}
else {
strncpy(exp, current, sizeof(exp) - 1);
exp[sizeof(exp) - 1] = '\0';
}
for (i = 0; *otrans != '\0';) {
if (*otrans == '\\') {
if (*(++otrans) == '\0')
break;
else
continue;
}
if (*otrans == ',') {
otrans++;
break;
}
next[i++] = *otrans++;
if (i >= MAX_REALM_LN) {
retval = KRB5KRB_AP_ERR_ILL_CR_TKT;
goto fail;
}
}
next[i] = '\0';
nlst = i - 1;
if (!strcmp(exp, realm)) added = TRUE;
if (!added) {
assert(nlst < 0 || nlst < sizeof(next));
if ((nlst < 0 || next[nlst] != '.') &&
(next[0] != '/') &&
(pl = subrealm(exp, realm))) {
added = TRUE;
current[sizeof(current) - 1] = '\0';
if (strlen(current) + (pl>0?pl:-pl) + 2 >= MAX_REALM_LN) {
retval = KRB5KRB_AP_ERR_ILL_CR_TKT;
goto fail;
}
strncat(current, ",", sizeof(current) - 1 - strlen(current));
if (pl > 0) {
strncat(current, realm, (unsigned) pl);
}
else {
strncat(current, realm+strlen(realm)+pl, (unsigned) (-pl));
}
}
else if ((pl = subrealm(realm, exp))) {
added = TRUE;
current[0] = '\0';
if ((pl1 = subrealm(prev,realm))) {
if (strlen(current) + (pl1>0?pl1:-pl1) + 1 >= MAX_REALM_LN) {
retval = KRB5KRB_AP_ERR_ILL_CR_TKT;
goto fail;
}
if (pl1 > 0) {
strncat(current, realm, (unsigned) pl1);
}
else {
strncat(current, realm+strlen(realm)+pl1, (unsigned) (-pl1));
}
}
else {
if ((realm[0] == '/') && prev[0]) {
if (strlen(current) + 2 >= MAX_REALM_LN) {
retval = KRB5KRB_AP_ERR_ILL_CR_TKT;
goto fail;
}
strncat(current, " ", sizeof(current) - 1 - strlen(current));
current[sizeof(current) - 1] = '\0';
}
if (strlen(current) + strlen(realm) + 1 >= MAX_REALM_LN) {
retval = KRB5KRB_AP_ERR_ILL_CR_TKT;
goto fail;
}
strncat(current, realm, sizeof(current) - 1 - strlen(current));
current[sizeof(current) - 1] = '\0';
}
if (strlen(current) + (pl>0?pl:-pl) + 2 >= MAX_REALM_LN) {
retval = KRB5KRB_AP_ERR_ILL_CR_TKT;
goto fail;
}
strncat(current,",", sizeof(current) - 1 - strlen(current));
current[sizeof(current) - 1] = '\0';
if (pl > 0) {
strncat(current, exp, (unsigned) pl);
}
else {
strncat(current, exp+strlen(exp)+pl, (unsigned)(-pl));
}
}
}
if (new_trans->length != 0) {
if (strlen(trans) + 2 >= MAX_REALM_LN) {
retval = KRB5KRB_AP_ERR_ILL_CR_TKT;
goto fail;
}
strcat(trans, ",");
}
if (strlen(trans) + strlen(current) + 1 >= MAX_REALM_LN) {
retval = KRB5KRB_AP_ERR_ILL_CR_TKT;
goto fail;
}
strcat(trans, current);
new_trans->length = strlen(trans);
strncpy(prev, exp, sizeof(prev) - 1);
prev[sizeof(prev) - 1] = '\0';
strncpy(current, next, sizeof(current) - 1);
current[sizeof(current) - 1] = '\0';
}
if (!added) {
if (new_trans->length != 0) {
if (strlen(trans) + 2 >= MAX_REALM_LN) {
retval = KRB5KRB_AP_ERR_ILL_CR_TKT;
goto fail;
}
strcat(trans, ",");
}
if((realm[0] == '/') && trans[0]) {
if (strlen(trans) + 2 >= MAX_REALM_LN) {
retval = KRB5KRB_AP_ERR_ILL_CR_TKT;
goto fail;
}
strcat(trans, " ");
}
if (strlen(trans) + strlen(realm) + 1 >= MAX_REALM_LN) {
retval = KRB5KRB_AP_ERR_ILL_CR_TKT;
goto fail;
}
strcat(trans, realm);
new_trans->length = strlen(trans);
}
retval = 0;
fail:
free(realm);
free(otrans_ptr);
return (retval);
}
#define AS_INVALID_OPTIONS (KDC_OPT_FORWARDED | KDC_OPT_PROXY |\
KDC_OPT_VALIDATE | KDC_OPT_RENEW | KDC_OPT_ENC_TKT_IN_SKEY)
int
validate_as_request(register krb5_kdc_req *request, krb5_db_entry client,
krb5_db_entry server, krb5_timestamp kdc_time,
const char **status)
{
int errcode;
if (request->kdc_options & AS_INVALID_OPTIONS) {
*status = "INVALID AS OPTIONS";
return KDC_ERR_BADOPTION;
}
if (client.pw_expiration && client.pw_expiration < kdc_time &&
!isflagset(server.attributes, KRB5_KDB_PWCHANGE_SERVICE)) {
*status = "CLIENT KEY EXPIRED";
#ifdef KRBCONF_VAGUE_ERRORS
return(KRB_ERR_GENERIC);
#else
return(KDC_ERR_KEY_EXP);
#endif
}
if (client.expiration && client.expiration < kdc_time) {
*status = "CLIENT EXPIRED";
#ifdef KRBCONF_VAGUE_ERRORS
return(KRB_ERR_GENERIC);
#else
return(KDC_ERR_NAME_EXP);
#endif
}
if (server.expiration && server.expiration < kdc_time) {
*status = "SERVICE EXPIRED";
return(KDC_ERR_SERVICE_EXP);
}
if (isflagset(client.attributes, KRB5_KDB_REQUIRES_PWCHANGE) &&
!isflagset(server.attributes, KRB5_KDB_PWCHANGE_SERVICE)) {
*status = "REQUIRED PWCHANGE";
return(KDC_ERR_KEY_EXP);
}
if ((isflagset(request->kdc_options, KDC_OPT_ALLOW_POSTDATE) ||
isflagset(request->kdc_options, KDC_OPT_POSTDATED)) &&
(isflagset(client.attributes, KRB5_KDB_DISALLOW_POSTDATED) ||
isflagset(server.attributes, KRB5_KDB_DISALLOW_POSTDATED))) {
*status = "POSTDATE NOT ALLOWED";
return(KDC_ERR_CANNOT_POSTDATE);
}
if (isflagset(request->kdc_options, KDC_OPT_FORWARDABLE) &&
(isflagset(client.attributes, KRB5_KDB_DISALLOW_FORWARDABLE) ||
isflagset(server.attributes, KRB5_KDB_DISALLOW_FORWARDABLE))) {
*status = "FORWARDABLE NOT ALLOWED";
return(KDC_ERR_POLICY);
}
if (isflagset(request->kdc_options, KDC_OPT_RENEWABLE) &&
(isflagset(client.attributes, KRB5_KDB_DISALLOW_RENEWABLE) ||
isflagset(server.attributes, KRB5_KDB_DISALLOW_RENEWABLE))) {
*status = "RENEWABLE NOT ALLOWED";
return(KDC_ERR_POLICY);
}
if (isflagset(request->kdc_options, KDC_OPT_PROXIABLE) &&
(isflagset(client.attributes, KRB5_KDB_DISALLOW_PROXIABLE) ||
isflagset(server.attributes, KRB5_KDB_DISALLOW_PROXIABLE))) {
*status = "PROXIABLE NOT ALLOWED";
return(KDC_ERR_POLICY);
}
if (isflagset(client.attributes, KRB5_KDB_DISALLOW_ALL_TIX)) {
*status = "CLIENT LOCKED OUT";
return(KDC_ERR_C_PRINCIPAL_UNKNOWN);
}
if (isflagset(server.attributes, KRB5_KDB_DISALLOW_ALL_TIX)) {
*status = "SERVICE LOCKED OUT";
return(KDC_ERR_S_PRINCIPAL_UNKNOWN);
}
if (isflagset(server.attributes, KRB5_KDB_DISALLOW_SVR)) {
*status = "SERVICE NOT ALLOWED";
return(KDC_ERR_S_PRINCIPAL_UNKNOWN);
}
errcode = against_local_policy_as(request, server, client,
kdc_time, status);
if (errcode)
return errcode;
return 0;
}
#define ASN1_ID_CLASS (0xc0)
#define ASN1_ID_TYPE (0x20)
#define ASN1_ID_TAG (0x1f)
#define ASN1_CLASS_UNIV (0)
#define ASN1_CLASS_APP (1)
#define ASN1_CLASS_CTX (2)
#define ASN1_CLASS_PRIV (3)
#define asn1_id_constructed(x) (x & ASN1_ID_TYPE)
#define asn1_id_primitive(x) (!asn1_id_constructed(x))
#define asn1_id_class(x) ((x & ASN1_ID_CLASS) >> 6)
#define asn1_id_tag(x) (x & ASN1_ID_TAG)
static int
asn1length(unsigned char **astream)
{
int length;
int sublen;
int blen;
unsigned char *p;
if (**astream & 0x80) {
blen = **astream & 0x7f;
if (blen > 3) {
return(-1);
}
for (++*astream, length = 0; blen; ++*astream, blen--) {
length = (length << 8) | **astream;
}
if (length == 0) {
p = *astream;
p++;
while (1) {
if ((sublen = asn1length(&p)) < 0) {
return(-1);
}
p += sublen;
if ((!*p++) && (!*p)) {
p++;
break;
}
}
length = p - *astream;
}
} else {
length = **astream;
++*astream;
}
return(length);
}
int
fetch_asn1_field(unsigned char *astream, unsigned int level,
unsigned int field, krb5_data *data)
{
unsigned char *estream;
int classes;
unsigned int levels = 0;
int lastlevel = 1000;
int length;
int tag;
unsigned char savelen;
classes = -1;
astream++;
estream = astream;
if ((length = asn1length(&astream)) < 0) {
return(-1);
}
estream += length;
while (astream < estream) {
if (!asn1_id_constructed(*astream)) {
return(-1);
}
if (asn1_id_class(*astream) == ASN1_CLASS_CTX) {
if ((tag = (int)asn1_id_tag(*astream)) <= lastlevel) {
levels++;
classes = -1;
}
lastlevel = tag;
if (levels == level) {
if (tag == field) {
astream++;
savelen = *astream;
if ((data->length = asn1length(&astream)) < 0) {
return(-1);
}
if ((savelen & 0xff) == 0x80) {
data->length -=2 ;
}
data->data = (char *)astream;
return(0);
} else if (tag <= classes) {
return(-1);
} else {
classes = tag;
}
}
}
astream++;
if ((length = asn1length(&astream)) < 0) {
return(-1);
}
if (levels == level) {
astream += length;
}
}
return(-1);
}
#define TGS_OPTIONS_HANDLED (KDC_OPT_FORWARDABLE | KDC_OPT_FORWARDED | \
KDC_OPT_PROXIABLE | KDC_OPT_PROXY | \
KDC_OPT_ALLOW_POSTDATE | KDC_OPT_POSTDATED | \
KDC_OPT_RENEWABLE | KDC_OPT_RENEWABLE_OK | \
KDC_OPT_ENC_TKT_IN_SKEY | KDC_OPT_RENEW | \
KDC_OPT_VALIDATE)
#define NO_TGT_OPTION (KDC_OPT_FORWARDED | KDC_OPT_PROXY | KDC_OPT_RENEW | \
KDC_OPT_VALIDATE)
int
validate_tgs_request(register krb5_kdc_req *request, krb5_db_entry server,
krb5_ticket *ticket, krb5_timestamp kdc_time,
const char **status)
{
int errcode;
int st_idx = 0;
request->kdc_options &= TGS_OPTIONS_HANDLED;
if (server.expiration && server.expiration < kdc_time) {
*status = "SERVICE EXPIRED";
return(KDC_ERR_SERVICE_EXP);
}
if (request->kdc_options & NO_TGT_OPTION) {
if (!krb5_principal_compare(kdc_context, ticket->server, request->server)) {
*status = "SERVER DIDN'T MATCH TICKET FOR RENEW/FORWARD/ETC";
return(KDC_ERR_SERVER_NOMATCH);
}
} else {
if (krb5_princ_size(kdc_context, ticket->server) != 2) {
*status = "BAD TGS SERVER LENGTH";
return KRB_AP_ERR_NOT_US;
}
if (!krb5_is_tgs_principal(ticket->server)) {
*status = "BAD TGS SERVER NAME";
return KRB_AP_ERR_NOT_US;
}
if ((krb5_princ_size(kdc_context, ticket->server) <= 1) ||
(krb5_princ_component(kdc_context, ticket->server, 1)->length !=
krb5_princ_realm(kdc_context, request->server)->length) ||
memcmp(krb5_princ_component(kdc_context, ticket->server, 1)->data,
krb5_princ_realm(kdc_context, request->server)->data,
krb5_princ_realm(kdc_context, request->server)->length)) {
*status = "BAD TGS SERVER INSTANCE";
return KRB_AP_ERR_NOT_US;
}
if (isflagset(server.attributes, KRB5_KDB_DISALLOW_TGT_BASED)) {
*status = "TGT BASED NOT ALLOWED";
return(KDC_ERR_POLICY);
}
}
if ((isflagset(request->kdc_options, KDC_OPT_FORWARDED) ||
isflagset(request->kdc_options, KDC_OPT_FORWARDABLE)) &&
!isflagset(ticket->enc_part2->flags, TKT_FLG_FORWARDABLE)) {
*status = "TGT NOT FORWARDABLE";
return KDC_ERR_BADOPTION;
}
if ((isflagset(request->kdc_options, KDC_OPT_PROXY) ||
isflagset(request->kdc_options, KDC_OPT_PROXIABLE)) &&
!isflagset(ticket->enc_part2->flags, TKT_FLG_PROXIABLE)) {
*status = "TGT NOT PROXIABLE";
return KDC_ERR_BADOPTION;
}
if ((isflagset(request->kdc_options, KDC_OPT_ALLOW_POSTDATE) ||
isflagset(request->kdc_options, KDC_OPT_POSTDATED)) &&
!isflagset(ticket->enc_part2->flags, TKT_FLG_MAY_POSTDATE)) {
*status = "TGT NOT POSTDATABLE";
return KDC_ERR_BADOPTION;
}
if (isflagset(request->kdc_options, KDC_OPT_VALIDATE) &&
!isflagset(ticket->enc_part2->flags, TKT_FLG_INVALID)) {
*status = "VALIDATE VALID TICKET";
return KDC_ERR_BADOPTION;
}
if ((isflagset(request->kdc_options, KDC_OPT_RENEW) ||
isflagset(request->kdc_options, KDC_OPT_RENEWABLE)) &&
!isflagset(ticket->enc_part2->flags, TKT_FLG_RENEWABLE)) {
*status = "TICKET NOT RENEWABLE";
return KDC_ERR_BADOPTION;
}
if (isflagset(request->kdc_options, KDC_OPT_PROXY) &&
(!request->server->data ||
request->server->data[0].length != KRB5_TGS_NAME_SIZE ||
memcmp(request->server->data[0].data, KRB5_TGS_NAME,
KRB5_TGS_NAME_SIZE))) {
*status = "CAN'T PROXY TGT";
return KDC_ERR_BADOPTION;
}
if (isflagset(request->kdc_options, KDC_OPT_FORWARDABLE) &&
isflagset(server.attributes, KRB5_KDB_DISALLOW_FORWARDABLE)) {
*status = "NON-FORWARDABLE TICKET";
return(KDC_ERR_POLICY);
}
if (isflagset(request->kdc_options, KDC_OPT_RENEWABLE) &&
isflagset(server.attributes, KRB5_KDB_DISALLOW_RENEWABLE)) {
*status = "NON-RENEWABLE TICKET";
return(KDC_ERR_POLICY);
}
if (isflagset(request->kdc_options, KDC_OPT_PROXIABLE) &&
isflagset(server.attributes, KRB5_KDB_DISALLOW_PROXIABLE)) {
*status = "NON-PROXIABLE TICKET";
return(KDC_ERR_POLICY);
}
if (isflagset(request->kdc_options, KDC_OPT_ALLOW_POSTDATE) &&
isflagset(server.attributes, KRB5_KDB_DISALLOW_POSTDATED)) {
*status = "NON-POSTDATABLE TICKET";
return(KDC_ERR_CANNOT_POSTDATE);
}
if (isflagset(request->kdc_options, KDC_OPT_ENC_TKT_IN_SKEY) &&
isflagset(server.attributes, KRB5_KDB_DISALLOW_DUP_SKEY)) {
*status = "DUP_SKEY DISALLOWED";
return(KDC_ERR_POLICY);
}
if (isflagset(server.attributes, KRB5_KDB_DISALLOW_ALL_TIX)) {
*status = "SERVER LOCKED OUT";
return(KDC_ERR_S_PRINCIPAL_UNKNOWN);
}
if (isflagset(server.attributes, KRB5_KDB_DISALLOW_SVR)) {
*status = "SERVER NOT ALLOWED";
return(KDC_ERR_S_PRINCIPAL_UNKNOWN);
}
if (check_hot_list(ticket)) {
*status = "HOT_LIST";
return(KRB_AP_ERR_REPEAT);
}
if (isflagset(request->kdc_options, KDC_OPT_VALIDATE)) {
if (ticket->enc_part2->times.starttime > kdc_time) {
*status = "NOT_YET_VALID";
return(KRB_AP_ERR_TKT_NYV);
}
}
if (isflagset(request->kdc_options, KDC_OPT_RENEW) &&
(ticket->enc_part2->times.renew_till < kdc_time)) {
*status = "TKT_EXPIRED";
return(KRB_AP_ERR_TKT_EXPIRED);
}
if (isflagset(request->kdc_options, KDC_OPT_ENC_TKT_IN_SKEY)) {
if (!request->second_ticket ||
!request->second_ticket[st_idx]) {
*status = "NO_2ND_TKT";
return(KDC_ERR_BADOPTION);
}
if (!krb5_principal_compare(kdc_context, request->second_ticket[st_idx]->server,
tgs_server)) {
*status = "2ND_TKT_NOT_TGS";
return(KDC_ERR_POLICY);
}
st_idx++;
}
if (isflagset(server.attributes, KRB5_KDB_REQUIRES_HW_AUTH) &&
!isflagset(ticket->enc_part2->flags,TKT_FLG_HW_AUTH)) {
*status = "NO HW PREAUTH";
return KRB_ERR_GENERIC;
}
if (isflagset(server.attributes, KRB5_KDB_REQUIRES_PRE_AUTH) &&
!isflagset(ticket->enc_part2->flags, TKT_FLG_PRE_AUTH)) {
*status = "NO PREAUTH";
return KRB_ERR_GENERIC;
}
errcode = against_local_policy_tgs(request, server, ticket, status);
if (errcode)
return errcode;
return 0;
}
int
dbentry_has_key_for_enctype(krb5_context context, krb5_db_entry *client,
krb5_enctype enctype)
{
krb5_error_code retval;
krb5_key_data *datap;
retval = krb5_dbe_find_enctype(context, client, enctype,
-1, 0, &datap);
if (retval)
return 0;
else
return 1;
}
int
dbentry_supports_enctype(krb5_context context, krb5_db_entry *client,
krb5_enctype enctype)
{
if (enctype == ENCTYPE_DES_CBC_MD5)
return 0;
if (enctype == ENCTYPE_DES_CBC_CRC)
return 1;
return dbentry_has_key_for_enctype(context, client, enctype);
}
krb5_enctype
select_session_keytype(krb5_context context, krb5_db_entry *server,
int nktypes, krb5_enctype *ktype)
{
int i;
for (i = 0; i < nktypes; i++) {
if (!krb5_c_valid_enctype(ktype[i]))
continue;
if (!krb5_is_permitted_enctype(context, ktype[i]))
continue;
if (dbentry_supports_enctype(context, server, ktype[i]))
return ktype[i];
}
return 0;
}
krb5_error_code
get_salt_from_key(krb5_context context, krb5_principal client,
krb5_key_data *client_key, krb5_data *salt)
{
krb5_error_code retval;
krb5_data * realm;
salt->data = 0;
salt->length = SALT_TYPE_NO_LENGTH;
if (client_key->key_data_ver == 1)
return 0;
switch (client_key->key_data_type[1]) {
case KRB5_KDB_SALTTYPE_NORMAL:
break;
case KRB5_KDB_SALTTYPE_V4:
salt->data = 0;
salt->length = 0;
break;
case KRB5_KDB_SALTTYPE_NOREALM:
if ((retval = krb5_principal2salt_norealm(context, client, salt)))
return retval;
break;
case KRB5_KDB_SALTTYPE_AFS3:
case KRB5_KDB_SALTTYPE_ONLYREALM:
realm = krb5_princ_realm(context, client);
salt->length = realm->length;
if ((salt->data = malloc(realm->length)) == NULL)
return ENOMEM;
memcpy(salt->data, realm->data, realm->length);
break;
case KRB5_KDB_SALTTYPE_SPECIAL:
salt->length = client_key->key_data_length[1];
if ((salt->data = malloc(salt->length)) == NULL)
return ENOMEM;
memcpy(salt->data, client_key->key_data_contents[1], salt->length);
break;
}
return 0;
}
#define NAME_LENGTH_LIMIT 128
void limit_string(char *name)
{
int i;
if (!name)
return;
if (strlen(name) < NAME_LENGTH_LIMIT)
return;
i = NAME_LENGTH_LIMIT-4;
name[i++] = '.';
name[i++] = '.';
name[i++] = '.';
name[i] = '\0';
return;
}
#define L10_2(x) ((int)(((x * 301) + 999) / 1000))
#define D_LEN(t) (L10_2(sizeof(t) * CHAR_BIT) + 2)
void
ktypes2str(char *s, size_t len, int nktypes, krb5_enctype *ktype)
{
int i;
char stmp[D_LEN(krb5_enctype) + 1];
char *p;
if (nktypes < 0
|| len < (sizeof(" etypes {...}") + D_LEN(int))) {
*s = '\0';
return;
}
sprintf(s, "%d etypes {", nktypes);
for (i = 0; i < nktypes; i++) {
sprintf(stmp, "%s%ld", i ? " " : "", (long)ktype[i]);
if (strlen(s) + strlen(stmp) + sizeof("}") > len)
break;
strcat(s, stmp);
}
if (i < nktypes) {
p = s + strlen(s);
while (p - s + sizeof("...}") > len) {
while (p > s && *p != ' ' && *p != '{')
*p-- = '\0';
if (p > s && *p == ' ') {
*p-- = '\0';
continue;
}
}
strcat(s, "...");
}
strcat(s, "}");
return;
}
void
rep_etypes2str(char *s, size_t len, krb5_kdc_rep *rep)
{
char stmp[sizeof("ses=") + D_LEN(krb5_enctype)];
if (len < (3 * D_LEN(krb5_enctype)
+ sizeof("etypes {rep= tkt= ses=}"))) {
*s = '\0';
return;
}
sprintf(s, "etypes {rep=%ld", (long)rep->enc_part.enctype);
if (rep->ticket != NULL) {
sprintf(stmp, " tkt=%ld", (long)rep->ticket->enc_part.enctype);
strcat(s, stmp);
}
if (rep->ticket != NULL
&& rep->ticket->enc_part2 != NULL
&& rep->ticket->enc_part2->session != NULL) {
sprintf(stmp, " ses=%ld",
(long)rep->ticket->enc_part2->session->enctype);
strcat(s, stmp);
}
strcat(s, "}");
return;
}