BOOL smb_krb5_principal_compare_any_realm(krb5_context context, krb5_const_principal princ1, krb5_const_principal princ2) { #ifdef HAVE_KRB5_PRINCIPAL_COMPARE_ANY_REALM return krb5_principal_compare_any_realm(context, princ1, princ2); /* krb5_princ_size is a macro in MIT */ #elif defined(HAVE_KRB5_PRINC_SIZE) || defined(krb5_princ_size) int i, len1, len2; const krb5_data *p1, *p2; len1 = krb5_princ_size(context, princ1); len2 = krb5_princ_size(context, princ2); if (len1 != len2) return False; for (i = 0; i < len1; i++) { p1 = krb5_princ_component(context, CONST_DISCARD(krb5_principal, princ1), i); p2 = krb5_princ_component(context, CONST_DISCARD(krb5_principal, princ2), i); if (p1->length != p2->length || memcmp(p1->data, p2->data, p1->length)) return False; } return True; #else #error NO_SUITABLE_PRINCIPAL_COMPARE_FUNCTION #endif }
/* * Convert a krb5_principal into the default salt for that principal. */ krb5_error_code ipa_krb5_principal2salt_norealm(krb5_context context, krb5_const_principal pr, krb5_data *ret) { unsigned int size = 0, offset=0; krb5_int32 nelem; register int i; if (pr == NULL) { ret->length = 0; ret->data = NULL; return 0; } nelem = krb5_princ_size(context, pr); for (i = 0; i < (int) nelem; i++) size += krb5_princ_component(context, pr, i)->length; ret->length = size; if (!(ret->data = malloc (size))) return ENOMEM; for (i = 0; i < (int) nelem; i++) { memcpy(&ret->data[offset], krb5_princ_component(context, pr, i)->data, krb5_princ_component(context, pr, i)->length); offset += krb5_princ_component(context, pr, i)->length; } return 0; }
/* * kdc_mcred() * * Return MCREDS for use as a match criterion. * * Resulting credential has CLIENT as the client principal, and * krbtgt/remote_realm(NXT_KDC)@local_realm(CUR_KDC) as the server * principal. Zeroes MCREDS first, does not allocate MCREDS, and * cleans MCREDS on failure. */ static krb5_error_code kdc_mcred(struct tr_state *ts, krb5_principal client, krb5_creds *mcreds) { krb5_error_code retval; krb5_data *rdst, *rsrc; retval = 0; memset(mcreds, 0, sizeof(*mcreds)); rdst = krb5_princ_component(ts->ctx, *ts->nxt_kdc, 1); rsrc = krb5_princ_component(ts->ctx, *ts->cur_kdc, 1); retval = krb5_copy_principal(ts->ctx, client, &mcreds->client); if (retval) goto cleanup; retval = krb5_tgtname(ts->ctx, rdst, rsrc, &mcreds->server); if (retval) goto cleanup; cleanup: if (retval) krb5_free_cred_contents(ts->ctx, mcreds); return retval; }
/* * find_nxt_kdc() * * A NXT_TGT gotten from an intermediate KDC might actually be a * referral. Search KDC_LIST forward starting from CUR_KDC, looking * for the KDC with the same remote realm as NXT_TGT. If we don't * find it, the intermediate KDC is leading us off the transit path. * * Match on CUR_KDC's remote realm, not local realm, because, among * other reasons, we can get a referral to the final realm; e.g., * given * * KDC_LIST == { krbtgt/R1@R1, krbtgt/R2@R1, krbtgt/R3@R2, * krbtgt/R4@R3, NULL } * CUR_TGT->SERVER == krbtgt/R2@R1 * NXT_TGT->SERVER == krbtgt/R4@R2 * * i.e., we got a ticket issued by R2 with remote realm R4, we want to * find krbtgt/R4@R3, not krbtgt/R3@R2, even though we have no TGT * with R3 as its local realm. * * Set up for next iteration of do_traversal() loop by pointing * NXT_KDC to one entry forward of the match. */ static krb5_error_code find_nxt_kdc(struct tr_state *ts) { krb5_data *r1, *r2; krb5_principal *kdcptr; TR_DBG(ts, "find_nxt_kdc"); assert(ts->ntgts > 0); assert(ts->nxt_tgt == ts->kdc_tgts[ts->ntgts-1]); if (krb5_princ_size(ts->ctx, ts->nxt_tgt->server) != 2) return KRB5_KDCREP_MODIFIED; r1 = krb5_princ_component(ts->ctx, ts->nxt_tgt->server, 1); for (kdcptr = ts->cur_kdc + 1; *kdcptr != NULL; kdcptr++) { r2 = krb5_princ_component(ts->ctx, *kdcptr, 1); if (r1 != NULL && r2 != NULL && data_eq(*r1, *r2)) { break; } } if (*kdcptr != NULL) { ts->nxt_kdc = kdcptr; TR_DBG_RET(ts, "find_nxt_kdc", 0); return 0; } r2 = krb5_princ_component(ts->ctx, ts->kdc_list[0], 1); if (r1 != NULL && r2 != NULL && r1->length == r2->length && !memcmp(r1->data, r2->data, r1->length)) { TR_DBG_RET(ts, "find_nxt_kdc: looped back to local", KRB5_KDCREP_MODIFIED); return KRB5_KDCREP_MODIFIED; } /* * Realm is not in our list; we probably got an unexpected realm * referral. */ ts->offpath_tgt = ts->nxt_tgt; if (ts->cur_kdc == ts->kdc_list) { /* * Local KDC referred us off path; trust it for caching * purposes. */ return 0; } /* * Unlink the off-path TGT from KDC_TGTS but don't free it, * because we should return it. */ ts->kdc_tgts[--ts->ntgts] = NULL; ts->nxt_tgt = ts->cur_tgt; TR_DBG_RET(ts, "find_nxt_kdc", 0); return 0; }
int compare_princ_comp(krb5_context context, krb5_principal princ, int n, char *ts) { krb5_data *obj = krb5_princ_component(context, princ, n); if (obj == NULL) return 0; return obj->length == strlen(ts) && !strncmp(obj->data, ts, obj->length); }
/* * Copy a principal structure, with fresh allocation. */ krb5_error_code KRB5_CALLCONV krb5_copy_principal(krb5_context context, krb5_const_principal inprinc, krb5_principal *outprinc) { register krb5_principal tempprinc; register int i, nelems; tempprinc = (krb5_principal)malloc(sizeof(krb5_principal_data)); if (tempprinc == 0) return ENOMEM; *tempprinc = *inprinc; nelems = (int) krb5_princ_size(context, inprinc); tempprinc->data = malloc(nelems * sizeof(krb5_data)); if (tempprinc->data == 0) { free((char *)tempprinc); return ENOMEM; } for (i = 0; i < nelems; i++) { if (krb5int_copy_data_contents(context, krb5_princ_component(context, inprinc, i), krb5_princ_component(context, tempprinc, i)) != 0) { while (--i >= 0) free(krb5_princ_component(context, tempprinc, i)->data); free (tempprinc->data); free (tempprinc); return ENOMEM; } } if (krb5int_copy_data_contents_add0(context, &inprinc->realm, &tempprinc->realm) != 0) { for (i = 0; i < nelems; i++) free(krb5_princ_component(context, tempprinc, i)->data); free(tempprinc->data); free(tempprinc); return ENOMEM; } *outprinc = tempprinc; return 0; }
char *dup_comp_string(krb5_context context, krb5_principal princ, int n) { krb5_data *obj = krb5_princ_component(context, princ, n); char *ret; if (obj == NULL) return NULL; ret=malloc(obj->length+1); memcpy(ret, obj->data, obj->length); ret[obj->length]=0; return ret; }
krb5_boolean KRB5_CALLCONV krb5_principal_compare(krb5_context context, krb5_const_principal princ1, krb5_const_principal princ2) { register int i; krb5_int32 nelem; nelem = krb5_princ_size(context, princ1); if (nelem != krb5_princ_size(context, princ2)) return FALSE; if (! krb5_realm_compare(context, princ1, princ2)) return FALSE; for (i = 0; i < (int) nelem; i++) { register const krb5_data *p1 = krb5_princ_component(context, princ1, i); register const krb5_data *p2 = krb5_princ_component(context, princ2, i); if (!data_eq(*p1, *p2)) return FALSE; } return TRUE; }
/* * offpath_loopchk() * * Check for loop back to previously-visited realms, both off-path and * on-path. */ static krb5_error_code offpath_loopchk(struct tr_state *ts, krb5_creds *tgt, krb5_creds *reftgts[], int rcount) { krb5_data *r1, *r2; int i; r1 = krb5_princ_component(ts->ctx, tgt->server, 1); for (i = 0; i < rcount; i++) { r2 = krb5_princ_component(ts->ctx, reftgts[i]->server, 1); if (r1->length == r2->length && !memcmp(r1->data, r2->data, r1->length)) return KRB5_KDCREP_MODIFIED; } for (i = 0; i < ts->ntgts; i++) { r2 = krb5_princ_component(ts->ctx, ts->kdc_tgts[i]->server, 1); if (r1->length == r2->length && !memcmp(r1->data, r2->data, r1->length)) return KRB5_KDCREP_MODIFIED; } return 0; }
/*ARGSUSED*/ static krb5_error_code krb5_principal2salt_internal(krb5_context context, register krb5_const_principal pr, krb5_data *ret, int use_realm) { unsigned int size = 0, offset = 0; krb5_int32 nelem; register int i; if (pr == 0) { ret->length = 0; ret->data = 0; return 0; } nelem = krb5_princ_size(context, pr); if (use_realm) size += krb5_princ_realm(context, pr)->length; for (i = 0; i < (int) nelem; i++) size += krb5_princ_component(context, pr, i)->length; ret->length = size; if (!(ret->data = malloc (size))) return ENOMEM; if (use_realm) { offset = krb5_princ_realm(context, pr)->length; memcpy(ret->data, krb5_princ_realm(context, pr)->data, offset); } for (i = 0; i < (int) nelem; i++) { memcpy(&ret->data[offset], krb5_princ_component(context, pr, i)->data, krb5_princ_component(context, pr, i)->length); offset += krb5_princ_component(context, pr, i)->length; } return 0; }
/* * The request seems to be for a ticket-granting service somewhere else, * but we don't have a ticket for the final TGS. Try to give the requestor * some intermediate realm. */ static krb5_error_code find_alternate_tgs(kdc_realm_t *kdc_active_realm, krb5_principal princ, krb5_db_entry **server_ptr, const char **status) { krb5_error_code retval; krb5_principal *plist = NULL, *pl2; krb5_data tmp; krb5_db_entry *server = NULL; *server_ptr = NULL; assert(is_cross_tgs_principal(princ)); if ((retval = krb5_walk_realm_tree(kdc_context, krb5_princ_realm(kdc_context, princ), krb5_princ_component(kdc_context, princ, 1), &plist, KRB5_REALM_BRANCH_CHAR))) { goto cleanup; } /* move to the end */ for (pl2 = plist; *pl2; pl2++); /* the first entry in this array is for krbtgt/local@local, so we ignore it */ while (--pl2 > plist) { tmp = *krb5_princ_realm(kdc_context, *pl2); krb5_princ_set_realm(kdc_context, *pl2, krb5_princ_realm(kdc_context, princ)); retval = db_get_svc_princ(kdc_context, *pl2, 0, &server, status); krb5_princ_set_realm(kdc_context, *pl2, &tmp); if (retval == KRB5_KDB_NOENTRY) continue; else if (retval) goto cleanup; log_tgs_alt_tgt(kdc_context, server->princ); *server_ptr = server; server = NULL; goto cleanup; } cleanup: if (retval == 0 && *server_ptr == NULL) retval = KRB5_KDB_NOENTRY; if (retval != 0) *status = "UNKNOWN_SERVER"; krb5_free_realm_tree(kdc_context, plist); krb5_db_free_principal(kdc_context, server); return retval; }
/* * Check whether the request satisfies the conditions for generating a referral * TGT. The caller checks whether the hostname component looks like a FQDN. */ static krb5_boolean is_referral_req(kdc_realm_t *kdc_active_realm, krb5_kdc_req *request) { krb5_boolean ret = FALSE; char *stype = NULL; char *ref_services = kdc_active_realm->realm_host_based_services; char *nonref_services = kdc_active_realm->realm_no_host_referral; if (!(request->kdc_options & KDC_OPT_CANONICALIZE)) return FALSE; if (request->kdc_options & KDC_OPT_ENC_TKT_IN_SKEY) return FALSE; if (krb5_princ_size(kdc_context, request->server) != 2) return FALSE; stype = data2string(krb5_princ_component(kdc_context, request->server, 0)); if (stype == NULL) return FALSE; switch (krb5_princ_type(kdc_context, request->server)) { case KRB5_NT_UNKNOWN: /* Allow referrals for NT-UNKNOWN principals, if configured. */ if (kdc_active_realm->realm_host_based_services != NULL) { if (!krb5_match_config_pattern(ref_services, stype) && !krb5_match_config_pattern(ref_services, KRB5_CONF_ASTERISK)) goto cleanup; } else goto cleanup; /* FALLTHROUGH */ case KRB5_NT_SRV_HST: case KRB5_NT_SRV_INST: /* Deny referrals for specific service types, if configured. */ if (kdc_active_realm->realm_no_host_referral != NULL) { if (krb5_match_config_pattern(nonref_services, stype)) goto cleanup; if (krb5_match_config_pattern(nonref_services, KRB5_CONF_ASTERISK)) goto cleanup; } ret = TRUE; break; default: goto cleanup; } cleanup: free(stype); return ret; }
void KRB5_CALLCONV krb5_free_principal(krb5_context context, krb5_principal val) { register krb5_int32 i; if (!val) return; if (val->data) { i = krb5_princ_size(context, val); while(--i >= 0) free(krb5_princ_component(context, val, i)->data); free(val->data); } free(val->realm.data); free(val); }
/* * Find a remote realm TGS principal for an unknown host-based service * principal. */ static krb5_int32 find_referral_tgs(kdc_realm_t *kdc_active_realm, krb5_kdc_req *request, krb5_principal *krbtgt_princ) { krb5_error_code retval = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; char **realms = NULL, *hostname = NULL; krb5_data srealm = request->server->realm; if (!is_referral_req(kdc_active_realm, request)) goto cleanup; hostname = data2string(krb5_princ_component(kdc_context, request->server, 1)); if (hostname == NULL) { retval = ENOMEM; goto cleanup; } /* If the hostname doesn't contain a '.', it's not a FQDN. */ if (strchr(hostname, '.') == NULL) goto cleanup; retval = krb5_get_host_realm(kdc_context, hostname, &realms); if (retval) { /* no match found */ kdc_err(kdc_context, retval, "unable to find realm of host"); goto cleanup; } /* Don't return a referral to the empty realm or the service realm. */ if (realms == NULL || realms[0] == NULL || *realms[0] == '\0' || data_eq_string(srealm, realms[0])) { retval = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; goto cleanup; } retval = krb5_build_principal(kdc_context, krbtgt_princ, srealm.length, srealm.data, "krbtgt", realms[0], (char *)0); cleanup: krb5_free_host_realm(kdc_context, realms); free(hostname); return retval; }
/* * Check whether the request satisfies the conditions for generating a referral * TGT. The caller checks whether the hostname component looks like a FQDN. */ static krb5_boolean is_referral_req(kdc_realm_t *kdc_active_realm, krb5_kdc_req *request) { krb5_boolean ret = FALSE; char *stype = NULL; char *hostbased = kdc_active_realm->realm_hostbased; char *no_referral = kdc_active_realm->realm_no_referral; if (!(request->kdc_options & KDC_OPT_CANONICALIZE)) return FALSE; if (request->kdc_options & KDC_OPT_ENC_TKT_IN_SKEY) return FALSE; if (krb5_princ_size(kdc_context, request->server) != 2) return FALSE; stype = data2string(krb5_princ_component(kdc_context, request->server, 0)); if (stype == NULL) return FALSE; switch (krb5_princ_type(kdc_context, request->server)) { case KRB5_NT_UNKNOWN: /* Allow referrals for NT-UNKNOWN principals, if configured. */ if (!in_list(hostbased, stype) && !in_list(hostbased, "*")) goto cleanup; /* FALLTHROUGH */ case KRB5_NT_SRV_HST: case KRB5_NT_SRV_INST: /* Deny referrals for specific service types, if configured. */ if (in_list(no_referral, stype) || in_list(no_referral, "*")) goto cleanup; ret = TRUE; break; default: goto cleanup; } cleanup: free(stype); return ret; }
kim_error kim_identity_get_component_at_index (kim_identity in_identity, kim_count in_index, kim_string *out_component_string) { kim_error err = KIM_NO_ERROR; krb5_data *component = NULL; if (!err && !in_identity ) { err = check_error (KIM_NULL_PARAMETER_ERR); } if (!err && !out_component_string) { err = check_error (KIM_NULL_PARAMETER_ERR); } if (!err) { krb5_int32 i = in_index; component = krb5_princ_component (in_identity->context, in_identity->principal, i); if (!component) { err = kim_error_set_message_for_code (KIM_BAD_COMPONENT_INDEX_ERR, i); } } if (!err) { err = kim_string_create_from_buffer (out_component_string, component->data, component->length); } return check_error (err); }
/* * chase_offpath() * * Chase off-path TGT referrals. * * If we are traversing a trusted path (either hierarchically derived * or explicit capath) and get a TGT pointing to a realm off this * path, query the realm referenced by that off-path TGT. Repeat * until we get to the destination realm or encounter an error. * * CUR_TGT is always either pointing into REFTGTS or is an alias for * TS->OFFPATH_TGT. */ static krb5_error_code chase_offpath(struct tr_state *ts, krb5_principal client, krb5_principal server) { krb5_error_code retval; krb5_creds mcred; krb5_creds *cur_tgt, *nxt_tgt, *reftgts[KRB5_REFERRAL_MAXHOPS]; krb5_data *rsrc, *rdst, *r1; int rcount, i; rdst = krb5_princ_realm(ts->ctx, server); cur_tgt = ts->offpath_tgt; for (rcount = 0; rcount < KRB5_REFERRAL_MAXHOPS; rcount++) { nxt_tgt = NULL; memset(&mcred, 0, sizeof(mcred)); rsrc = krb5_princ_component(ts->ctx, cur_tgt->server, 1); retval = krb5_tgtname(ts->ctx, rdst, rsrc, &mcred.server); if (retval) goto cleanup; mcred.client = client; retval = krb5_get_cred_via_tkt(ts->ctx, cur_tgt, FLAGS2OPTS(cur_tgt->ticket_flags), cur_tgt->addresses, &mcred, &nxt_tgt); mcred.client = NULL; krb5_free_principal(ts->ctx, mcred.server); mcred.server = NULL; if (retval) goto cleanup; if (!IS_TGS_PRINC(ts->ctx, nxt_tgt->server)) { retval = KRB5_KDCREP_MODIFIED; goto cleanup; } r1 = krb5_princ_component(ts->ctx, nxt_tgt->server, 1); if (rdst->length == r1->length && !memcmp(rdst->data, r1->data, rdst->length)) { retval = 0; goto cleanup; } retval = offpath_loopchk(ts, nxt_tgt, reftgts, rcount); if (retval) goto cleanup; reftgts[rcount] = nxt_tgt; cur_tgt = nxt_tgt; nxt_tgt = NULL; } /* Max hop count exceeded. */ retval = KRB5_KDCREP_MODIFIED; cleanup: if (mcred.server != NULL) { krb5_free_principal(ts->ctx, mcred.server); } /* * Don't free TS->OFFPATH_TGT if it's in the list of cacheable * TGTs to be returned by do_traversal(). */ if (ts->offpath_tgt != ts->nxt_tgt) { krb5_free_creds(ts->ctx, ts->offpath_tgt); } ts->offpath_tgt = NULL; if (nxt_tgt != NULL) { if (retval) krb5_free_creds(ts->ctx, nxt_tgt); else ts->offpath_tgt = nxt_tgt; } for (i = 0; i < rcount; i++) { krb5_free_creds(ts->ctx, reftgts[i]); } return retval; }
/*ARGSUSED*/ krb5_error_code process_tgs_req(krb5_data *pkt, const krb5_fulladdr *from, krb5_data **response) { krb5_keyblock * subkey = 0; krb5_kdc_req *request = 0; krb5_db_entry server; krb5_kdc_rep reply; krb5_enc_kdc_rep_part reply_encpart; krb5_ticket ticket_reply, *header_ticket = 0; int st_idx = 0; krb5_enc_tkt_part enc_tkt_reply; krb5_transited enc_tkt_transited; int newtransited = 0; krb5_error_code retval = 0; int nprincs = 0; krb5_boolean more; krb5_timestamp kdc_time, authtime=0; krb5_keyblock session_key; krb5_timestamp until, rtime; krb5_keyblock encrypting_key; krb5_key_data *server_key; char *cname = 0, *sname = 0, *tmp = 0; const char *fromstring = 0; krb5_last_req_entry *nolrarray[2], nolrentry; /* krb5_address *noaddrarray[1]; */ krb5_enctype useenctype; int errcode, errcode2; register int i; int firstpass = 1; const char *status = 0; char ktypestr[128]; char rep_etypestr[128]; char fromstringbuf[70]; session_key.contents = 0; retval = decode_krb5_tgs_req(pkt, &request); if (retval) return retval; if (request->msg_type != KRB5_TGS_REQ) return KRB5_BADMSGTYPE; ktypes2str(ktypestr, sizeof(ktypestr), request->nktypes, request->ktype); /* * setup_server_realm() sets up the global realm-specific data pointer. */ if ((retval = setup_server_realm(request->server))) { krb5_free_kdc_req(kdc_context, request); return retval; } fromstring = inet_ntop(ADDRTYPE2FAMILY(from->address->addrtype), from->address->contents, fromstringbuf, sizeof(fromstringbuf)); if (!fromstring) fromstring = "<unknown>"; if ((errcode = krb5_unparse_name(kdc_context, request->server, &sname))) { status = "UNPARSING SERVER"; goto cleanup; } limit_string(sname); /* errcode = kdc_process_tgs_req(request, from, pkt, &req_authdat); */ errcode = kdc_process_tgs_req(request, from, pkt, &header_ticket, &subkey); if (header_ticket && header_ticket->enc_part2 && (errcode2 = krb5_unparse_name(kdc_context, header_ticket->enc_part2->client, &cname))) { status = "UNPARSING CLIENT"; errcode = errcode2; goto cleanup; } limit_string(cname); if (errcode) { status = "PROCESS_TGS"; goto cleanup; } if (!header_ticket) { errcode = KRB5_NO_TKT_SUPPLIED; /* XXX? */ status="UNEXPECTED NULL in header_ticket"; goto cleanup; } /* * We've already dealt with the AP_REQ authentication, so we can * use header_ticket freely. The encrypted part (if any) has been * decrypted with the session key. */ authtime = header_ticket->enc_part2->times.authtime; /* XXX make sure server here has the proper realm...taken from AP_REQ header? */ nprincs = 1; if ((errcode = get_principal(kdc_context, request->server, &server, &nprincs, &more))) { status = "LOOKING_UP_SERVER"; nprincs = 0; goto cleanup; } tgt_again: if (more) { status = "NON_UNIQUE_PRINCIPAL"; errcode = KRB5KDC_ERR_PRINCIPAL_NOT_UNIQUE; goto cleanup; } else if (nprincs != 1) { /* * might be a request for a TGT for some other realm; we * should do our best to find such a TGS in this db */ if (firstpass && krb5_is_tgs_principal(request->server) == TRUE) { if (krb5_princ_size(kdc_context, request->server) == 2) { krb5_data *server_1 = krb5_princ_component(kdc_context, request->server, 1); krb5_data *tgs_1 = krb5_princ_component(kdc_context, tgs_server, 1); if (!tgs_1 || !data_eq(*server_1, *tgs_1)) { krb5_db_free_principal(kdc_context, &server, nprincs); find_alternate_tgs(request, &server, &more, &nprincs); firstpass = 0; goto tgt_again; } } } krb5_db_free_principal(kdc_context, &server, nprincs); status = "UNKNOWN_SERVER"; errcode = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; goto cleanup; } if ((errcode = krb5_timeofday(kdc_context, &kdc_time))) { status = "TIME_OF_DAY"; goto cleanup; } if ((retval = validate_tgs_request(request, server, header_ticket, kdc_time, &status))) { if (!status) status = "UNKNOWN_REASON"; errcode = retval + ERROR_TABLE_BASE_krb5; goto cleanup; } /* * We pick the session keytype here.... * * Some special care needs to be taken in the user-to-user * case, since we don't know what keytypes the application server * which is doing user-to-user authentication can support. We * know that it at least must be able to support the encryption * type of the session key in the TGT, since otherwise it won't be * able to decrypt the U2U ticket! So we use that in preference * to anything else. */ useenctype = 0; if (isflagset(request->kdc_options, KDC_OPT_ENC_TKT_IN_SKEY)) { krb5_keyblock * st_sealing_key; krb5_kvno st_srv_kvno; krb5_enctype etype; /* * Get the key for the second ticket, and decrypt it. */ if ((errcode = kdc_get_server_key(request->second_ticket[st_idx], &st_sealing_key, &st_srv_kvno))) { status = "2ND_TKT_SERVER"; goto cleanup; } errcode = krb5_decrypt_tkt_part(kdc_context, st_sealing_key, request->second_ticket[st_idx]); krb5_free_keyblock(kdc_context, st_sealing_key); if (errcode) { status = "2ND_TKT_DECRYPT"; goto cleanup; } etype = request->second_ticket[st_idx]->enc_part2->session->enctype; if (!krb5_c_valid_enctype(etype)) { status = "BAD_ETYPE_IN_2ND_TKT"; errcode = KRB5KDC_ERR_ETYPE_NOSUPP; goto cleanup; } for (i = 0; i < request->nktypes; i++) { if (request->ktype[i] == etype) { useenctype = etype; break; } } } /* * Select the keytype for the ticket session key. */ if ((useenctype == 0) && (useenctype = select_session_keytype(kdc_context, &server, request->nktypes, request->ktype)) == 0) { /* unsupported ktype */ status = "BAD_ENCRYPTION_TYPE"; errcode = KRB5KDC_ERR_ETYPE_NOSUPP; goto cleanup; } errcode = krb5_c_make_random_key(kdc_context, useenctype, &session_key); if (errcode) { /* random key failed */ status = "RANDOM_KEY_FAILED"; goto cleanup; } ticket_reply.server = request->server; /* XXX careful for realm... */ enc_tkt_reply.flags = 0; enc_tkt_reply.times.starttime = 0; /* * Fix header_ticket's starttime; if it's zero, fill in the * authtime's value. */ if (!(header_ticket->enc_part2->times.starttime)) header_ticket->enc_part2->times.starttime = header_ticket->enc_part2->times.authtime; /* don't use new addresses unless forwarded, see below */ enc_tkt_reply.caddrs = header_ticket->enc_part2->caddrs; /* noaddrarray[0] = 0; */ reply_encpart.caddrs = 0; /* optional...don't put it in */ /* It should be noted that local policy may affect the */ /* processing of any of these flags. For example, some */ /* realms may refuse to issue renewable tickets */ if (isflagset(request->kdc_options, KDC_OPT_FORWARDABLE)) setflag(enc_tkt_reply.flags, TKT_FLG_FORWARDABLE); if (isflagset(request->kdc_options, KDC_OPT_FORWARDED)) { setflag(enc_tkt_reply.flags, TKT_FLG_FORWARDED); /* include new addresses in ticket & reply */ enc_tkt_reply.caddrs = request->addresses; reply_encpart.caddrs = request->addresses; } if (isflagset(header_ticket->enc_part2->flags, TKT_FLG_FORWARDED)) setflag(enc_tkt_reply.flags, TKT_FLG_FORWARDED); if (isflagset(request->kdc_options, KDC_OPT_PROXIABLE)) setflag(enc_tkt_reply.flags, TKT_FLG_PROXIABLE); if (isflagset(request->kdc_options, KDC_OPT_PROXY)) { setflag(enc_tkt_reply.flags, TKT_FLG_PROXY); /* include new addresses in ticket & reply */ enc_tkt_reply.caddrs = request->addresses; reply_encpart.caddrs = request->addresses; } if (isflagset(request->kdc_options, KDC_OPT_ALLOW_POSTDATE)) setflag(enc_tkt_reply.flags, TKT_FLG_MAY_POSTDATE); if (isflagset(request->kdc_options, KDC_OPT_POSTDATED)) { setflag(enc_tkt_reply.flags, TKT_FLG_POSTDATED); setflag(enc_tkt_reply.flags, TKT_FLG_INVALID); enc_tkt_reply.times.starttime = request->from; } else enc_tkt_reply.times.starttime = kdc_time; if (isflagset(request->kdc_options, KDC_OPT_VALIDATE)) { /* BEWARE of allocation hanging off of ticket & enc_part2, it belongs to the caller */ ticket_reply = *(header_ticket); enc_tkt_reply = *(header_ticket->enc_part2); enc_tkt_reply.authorization_data = NULL; clear(enc_tkt_reply.flags, TKT_FLG_INVALID); } if (isflagset(request->kdc_options, KDC_OPT_RENEW)) { krb5_deltat old_life; /* BEWARE of allocation hanging off of ticket & enc_part2, it belongs to the caller */ ticket_reply = *(header_ticket); enc_tkt_reply = *(header_ticket->enc_part2); enc_tkt_reply.authorization_data = NULL; old_life = enc_tkt_reply.times.endtime - enc_tkt_reply.times.starttime; enc_tkt_reply.times.starttime = kdc_time; enc_tkt_reply.times.endtime = min(header_ticket->enc_part2->times.renew_till, kdc_time + old_life); } else { /* not a renew request */ enc_tkt_reply.times.starttime = kdc_time; until = (request->till == 0) ? kdc_infinity : request->till; enc_tkt_reply.times.endtime = min(until, min(enc_tkt_reply.times.starttime + server.max_life, min(enc_tkt_reply.times.starttime + max_life_for_realm, header_ticket->enc_part2->times.endtime))); if (isflagset(request->kdc_options, KDC_OPT_RENEWABLE_OK) && (enc_tkt_reply.times.endtime < request->till) && isflagset(header_ticket->enc_part2->flags, TKT_FLG_RENEWABLE)) { setflag(request->kdc_options, KDC_OPT_RENEWABLE); request->rtime = min(request->till, header_ticket->enc_part2->times.renew_till); } } rtime = (request->rtime == 0) ? kdc_infinity : request->rtime; if (isflagset(request->kdc_options, KDC_OPT_RENEWABLE)) { /* already checked above in policy check to reject request for a renewable ticket using a non-renewable ticket */ setflag(enc_tkt_reply.flags, TKT_FLG_RENEWABLE); enc_tkt_reply.times.renew_till = min(rtime, min(header_ticket->enc_part2->times.renew_till, enc_tkt_reply.times.starttime + min(server.max_renewable_life, max_renewable_life_for_realm))); } else { enc_tkt_reply.times.renew_till = 0; } /* * Set authtime to be the same as header_ticket's */ enc_tkt_reply.times.authtime = header_ticket->enc_part2->times.authtime; /* * Propagate the preauthentication flags through to the returned ticket. */ if (isflagset(header_ticket->enc_part2->flags, TKT_FLG_PRE_AUTH)) setflag(enc_tkt_reply.flags, TKT_FLG_PRE_AUTH); if (isflagset(header_ticket->enc_part2->flags, TKT_FLG_HW_AUTH)) setflag(enc_tkt_reply.flags, TKT_FLG_HW_AUTH); /* starttime is optional, and treated as authtime if not present. so we can nuke it if it matches */ if (enc_tkt_reply.times.starttime == enc_tkt_reply.times.authtime) enc_tkt_reply.times.starttime = 0; /* assemble any authorization data */ if (request->authorization_data.ciphertext.data) { krb5_data scratch; scratch.length = request->authorization_data.ciphertext.length; if (!(scratch.data = malloc(request->authorization_data.ciphertext.length))) { status = "AUTH_NOMEM"; errcode = ENOMEM; goto cleanup; } if ((errcode = krb5_c_decrypt(kdc_context, header_ticket->enc_part2->session, KRB5_KEYUSAGE_TGS_REQ_AD_SESSKEY, 0, &request->authorization_data, &scratch))) { status = "AUTH_ENCRYPT_FAIL"; free(scratch.data); goto cleanup; } /* scratch now has the authorization data, so we decode it */ errcode = decode_krb5_authdata(&scratch, &(request->unenc_authdata)); free(scratch.data); if (errcode) { status = "AUTH_DECODE"; goto cleanup; } if ((errcode = concat_authorization_data(request->unenc_authdata, header_ticket->enc_part2->authorization_data, &enc_tkt_reply.authorization_data))) { status = "CONCAT_AUTH"; goto cleanup; } } else enc_tkt_reply.authorization_data = header_ticket->enc_part2->authorization_data; enc_tkt_reply.session = &session_key; enc_tkt_reply.client = header_ticket->enc_part2->client; enc_tkt_reply.transited.tr_type = KRB5_DOMAIN_X500_COMPRESS; enc_tkt_reply.transited.tr_contents = empty_string; /* equivalent of "" */ /* * Only add the realm of the presented tgt to the transited list if * it is different than the local realm (cross-realm) and it is different * than the realm of the client (since the realm of the client is already * implicitly part of the transited list and should not be explicitly * listed). */ /* realm compare is like strcmp, but knows how to deal with these args */ if (realm_compare(header_ticket->server, tgs_server) || realm_compare(header_ticket->server, enc_tkt_reply.client)) { /* tgt issued by local realm or issued by realm of client */ enc_tkt_reply.transited = header_ticket->enc_part2->transited; } else { /* tgt issued by some other realm and not the realm of the client */ /* assemble new transited field into allocated storage */ if (header_ticket->enc_part2->transited.tr_type != KRB5_DOMAIN_X500_COMPRESS) { status = "BAD_TRTYPE"; errcode = KRB5KDC_ERR_TRTYPE_NOSUPP; goto cleanup; } enc_tkt_transited.tr_type = KRB5_DOMAIN_X500_COMPRESS; enc_tkt_transited.magic = 0; enc_tkt_transited.tr_contents.magic = 0; enc_tkt_transited.tr_contents.data = 0; enc_tkt_transited.tr_contents.length = 0; enc_tkt_reply.transited = enc_tkt_transited; if ((errcode = add_to_transited(&header_ticket->enc_part2->transited.tr_contents, &enc_tkt_reply.transited.tr_contents, header_ticket->server, enc_tkt_reply.client, request->server))) { status = "ADD_TR_FAIL"; goto cleanup; } newtransited = 1; } if (!isflagset (request->kdc_options, KDC_OPT_DISABLE_TRANSITED_CHECK)) { unsigned int tlen; char *tdots; errcode = krb5_check_transited_list (kdc_context, &enc_tkt_reply.transited.tr_contents, krb5_princ_realm (kdc_context, header_ticket->enc_part2->client), krb5_princ_realm (kdc_context, request->server)); tlen = enc_tkt_reply.transited.tr_contents.length; tdots = tlen > 125 ? "..." : ""; tlen = tlen > 125 ? 125 : tlen; if (errcode == 0) { setflag (enc_tkt_reply.flags, TKT_FLG_TRANSIT_POLICY_CHECKED); } else if (errcode == KRB5KRB_AP_ERR_ILL_CR_TKT) krb5_klog_syslog (LOG_INFO, "bad realm transit path from '%s' to '%s' " "via '%.*s%s'", cname ? cname : "<unknown client>", sname ? sname : "<unknown server>", tlen, enc_tkt_reply.transited.tr_contents.data, tdots); else { const char *emsg = krb5_get_error_message(kdc_context, errcode); krb5_klog_syslog (LOG_ERR, "unexpected error checking transit from " "'%s' to '%s' via '%.*s%s': %s", cname ? cname : "<unknown client>", sname ? sname : "<unknown server>", tlen, enc_tkt_reply.transited.tr_contents.data, tdots, emsg); krb5_free_error_message(kdc_context, emsg); } } else krb5_klog_syslog (LOG_INFO, "not checking transit path"); if (reject_bad_transit && !isflagset (enc_tkt_reply.flags, TKT_FLG_TRANSIT_POLICY_CHECKED)) { errcode = KRB5KDC_ERR_POLICY; status = "BAD_TRANSIT"; goto cleanup; } ticket_reply.enc_part2 = &enc_tkt_reply; /* * If we are doing user-to-user authentication, then make sure * that the client for the second ticket matches the request * server, and then encrypt the ticket using the session key of * the second ticket. */ if (isflagset(request->kdc_options, KDC_OPT_ENC_TKT_IN_SKEY)) { /* * Make sure the client for the second ticket matches * requested server. */ krb5_enc_tkt_part *t2enc = request->second_ticket[st_idx]->enc_part2; krb5_principal client2 = t2enc->client; if (!krb5_principal_compare(kdc_context, request->server, client2)) { if ((errcode = krb5_unparse_name(kdc_context, client2, &tmp))) tmp = 0; if (tmp != NULL) limit_string(tmp); krb5_klog_syslog(LOG_INFO, "TGS_REQ %s: 2ND_TKT_MISMATCH: " "authtime %d, %s for %s, 2nd tkt client %s", fromstring, authtime, cname ? cname : "<unknown client>", sname ? sname : "<unknown server>", tmp ? tmp : "<unknown>"); errcode = KRB5KDC_ERR_SERVER_NOMATCH; goto cleanup; } ticket_reply.enc_part.kvno = 0; ticket_reply.enc_part.enctype = t2enc->session->enctype; if ((errcode = krb5_encrypt_tkt_part(kdc_context, t2enc->session, &ticket_reply))) { status = "2ND_TKT_ENCRYPT"; goto cleanup; } st_idx++; } else { /* * Find the server key */ if ((errcode = krb5_dbe_find_enctype(kdc_context, &server, -1, /* ignore keytype */ -1, /* Ignore salttype */ 0, /* Get highest kvno */ &server_key))) { status = "FINDING_SERVER_KEY"; goto cleanup; } /* convert server.key into a real key (it may be encrypted * in the database) */ if ((errcode = krb5_dbekd_decrypt_key_data(kdc_context, &master_keyblock, server_key, &encrypting_key, NULL))) { status = "DECRYPT_SERVER_KEY"; goto cleanup; } errcode = krb5_encrypt_tkt_part(kdc_context, &encrypting_key, &ticket_reply); krb5_free_keyblock_contents(kdc_context, &encrypting_key); if (errcode) { status = "TKT_ENCRYPT"; goto cleanup; } ticket_reply.enc_part.kvno = server_key->key_data_kvno; } /* Start assembling the response */ reply.msg_type = KRB5_TGS_REP; reply.padata = 0; /* always */ reply.client = header_ticket->enc_part2->client; reply.enc_part.kvno = 0; /* We are using the session key */ reply.ticket = &ticket_reply; reply_encpart.session = &session_key; reply_encpart.nonce = request->nonce; /* copy the time fields EXCEPT for authtime; its location is used for ktime */ reply_encpart.times = enc_tkt_reply.times; reply_encpart.times.authtime = header_ticket->enc_part2->times.authtime; /* starttime is optional, and treated as authtime if not present. so we can nuke it if it matches */ if (enc_tkt_reply.times.starttime == enc_tkt_reply.times.authtime) enc_tkt_reply.times.starttime = 0; nolrentry.lr_type = KRB5_LRQ_NONE; nolrentry.value = 0; nolrarray[0] = &nolrentry; nolrarray[1] = 0; reply_encpart.last_req = nolrarray; /* not available for TGS reqs */ reply_encpart.key_exp = 0; /* ditto */ reply_encpart.flags = enc_tkt_reply.flags; reply_encpart.server = ticket_reply.server; /* use the session key in the ticket, unless there's a subsession key in the AP_REQ */ reply.enc_part.enctype = subkey ? subkey->enctype : header_ticket->enc_part2->session->enctype; errcode = krb5_encode_kdc_rep(kdc_context, KRB5_TGS_REP, &reply_encpart, subkey ? 1 : 0, subkey ? subkey : header_ticket->enc_part2->session, &reply, response); if (errcode) { status = "ENCODE_KDC_REP"; } else { status = "ISSUE"; } memset(ticket_reply.enc_part.ciphertext.data, 0, ticket_reply.enc_part.ciphertext.length); free(ticket_reply.enc_part.ciphertext.data); /* these parts are left on as a courtesy from krb5_encode_kdc_rep so we can use them in raw form if needed. But, we don't... */ memset(reply.enc_part.ciphertext.data, 0, reply.enc_part.ciphertext.length); free(reply.enc_part.ciphertext.data); cleanup: if (status) { const char * emsg = NULL; if (!errcode) rep_etypes2str(rep_etypestr, sizeof(rep_etypestr), &reply); if (errcode) emsg = krb5_get_error_message (kdc_context, errcode); krb5_klog_syslog(LOG_INFO, "TGS_REQ (%s) %s: %s: authtime %d, " "%s%s %s for %s%s%s", ktypestr, fromstring, status, authtime, !errcode ? rep_etypestr : "", !errcode ? "," : "", cname ? cname : "<unknown client>", sname ? sname : "<unknown server>", errcode ? ", " : "", errcode ? emsg : ""); if (errcode) krb5_free_error_message (kdc_context, emsg); } if (errcode) { int got_err = 0; if (status == 0) { status = krb5_get_error_message (kdc_context, errcode); got_err = 1; } errcode -= ERROR_TABLE_BASE_krb5; if (errcode < 0 || errcode > 128) errcode = KRB_ERR_GENERIC; retval = prepare_error_tgs(request, header_ticket, errcode, fromstring, response, status); if (got_err) { krb5_free_error_message (kdc_context, status); status = 0; } } if (header_ticket) krb5_free_ticket(kdc_context, header_ticket); if (request) krb5_free_kdc_req(kdc_context, request); if (cname) free(cname); if (sname) free(sname); if (nprincs) krb5_db_free_principal(kdc_context, &server, 1); if (session_key.contents) krb5_free_keyblock_contents(kdc_context, &session_key); if (newtransited) free(enc_tkt_reply.transited.tr_contents.data); if (subkey) krb5_free_keyblock(kdc_context, subkey); return retval; }
NTSTATUS ads_verify_ticket(TALLOC_CTX *mem_ctx, const char *realm, time_t time_offset, const DATA_BLOB *ticket, char **principal, struct PAC_DATA **pac_data, DATA_BLOB *ap_rep, DATA_BLOB *session_key, bool use_replay_cache) { NTSTATUS sret = NT_STATUS_LOGON_FAILURE; NTSTATUS pac_ret; DATA_BLOB auth_data; krb5_context context = NULL; krb5_auth_context auth_context = NULL; krb5_data packet; krb5_ticket *tkt = NULL; krb5_rcache rcache = NULL; krb5_keyblock *keyblock = NULL; time_t authtime; krb5_error_code ret = 0; int flags = 0; krb5_principal host_princ = NULL; krb5_const_principal client_principal = NULL; char *host_princ_s = NULL; bool auth_ok = False; bool got_auth_data = False; struct named_mutex *mutex = NULL; ZERO_STRUCT(packet); ZERO_STRUCT(auth_data); *principal = NULL; *pac_data = NULL; *ap_rep = data_blob_null; *session_key = data_blob_null; initialize_krb5_error_table(); ret = krb5_init_context(&context); if (ret) { DEBUG(1,("ads_verify_ticket: krb5_init_context failed (%s)\n", error_message(ret))); return NT_STATUS_LOGON_FAILURE; } if (time_offset != 0) { krb5_set_real_time(context, time(NULL) + time_offset, 0); } ret = krb5_set_default_realm(context, realm); if (ret) { DEBUG(1,("ads_verify_ticket: krb5_set_default_realm failed (%s)\n", error_message(ret))); goto out; } /* This whole process is far more complex than I would like. We have to go through all this to allow us to store the secret internally, instead of using /etc/krb5.keytab */ ret = krb5_auth_con_init(context, &auth_context); if (ret) { DEBUG(1,("ads_verify_ticket: krb5_auth_con_init failed (%s)\n", error_message(ret))); goto out; } krb5_auth_con_getflags( context, auth_context, &flags ); if ( !use_replay_cache ) { /* Disable default use of a replay cache */ flags &= ~KRB5_AUTH_CONTEXT_DO_TIME; krb5_auth_con_setflags( context, auth_context, flags ); } if (asprintf(&host_princ_s, "%s$", global_myname()) == -1) { goto out; } strlower_m(host_princ_s); ret = smb_krb5_parse_name(context, host_princ_s, &host_princ); if (ret) { DEBUG(1,("ads_verify_ticket: smb_krb5_parse_name(%s) failed (%s)\n", host_princ_s, error_message(ret))); goto out; } if ( use_replay_cache ) { /* Lock a mutex surrounding the replay as there is no locking in the MIT krb5 code surrounding the replay cache... */ mutex = grab_named_mutex(talloc_tos(), "replay cache mutex", 10); if (mutex == NULL) { DEBUG(1,("ads_verify_ticket: unable to protect " "replay cache with mutex.\n")); ret = KRB5_CC_IO; goto out; } /* JRA. We must set the rcache here. This will prevent replay attacks. */ ret = krb5_get_server_rcache(context, krb5_princ_component(context, host_princ, 0), &rcache); if (ret) { DEBUG(1,("ads_verify_ticket: krb5_get_server_rcache " "failed (%s)\n", error_message(ret))); goto out; } ret = krb5_auth_con_setrcache(context, auth_context, rcache); if (ret) { DEBUG(1,("ads_verify_ticket: krb5_auth_con_setrcache " "failed (%s)\n", error_message(ret))); goto out; } } /* Try secrets.tdb first and fallback to the krb5.keytab if necessary */ auth_ok = ads_secrets_verify_ticket(context, auth_context, host_princ, ticket, &tkt, &keyblock, &ret); if (!auth_ok && (ret == KRB5KRB_AP_ERR_TKT_NYV || ret == KRB5KRB_AP_ERR_TKT_EXPIRED || ret == KRB5KRB_AP_ERR_SKEW)) { goto auth_failed; } if (!auth_ok && lp_use_kerberos_keytab()) { auth_ok = ads_keytab_verify_ticket(context, auth_context, ticket, &tkt, &keyblock, &ret); } if ( use_replay_cache ) { TALLOC_FREE(mutex); #if 0 /* Heimdal leaks here, if we fix the leak, MIT crashes */ if (rcache) { krb5_rc_close(context, rcache); } #endif } auth_failed: if (!auth_ok) { DEBUG(3,("ads_verify_ticket: krb5_rd_req with auth failed (%s)\n", error_message(ret))); /* Try map the error return in case it's something like * a clock skew error. */ sret = krb5_to_nt_status(ret); if (NT_STATUS_IS_OK(sret) || NT_STATUS_EQUAL(sret,NT_STATUS_UNSUCCESSFUL)) { sret = NT_STATUS_LOGON_FAILURE; } DEBUG(10,("ads_verify_ticket: returning error %s\n", nt_errstr(sret) )); goto out; } authtime = get_authtime_from_tkt(tkt); client_principal = get_principal_from_tkt(tkt); ret = krb5_mk_rep(context, auth_context, &packet); if (ret) { DEBUG(3,("ads_verify_ticket: Failed to generate mutual authentication reply (%s)\n", error_message(ret))); goto out; } *ap_rep = data_blob(packet.data, packet.length); if (packet.data) { kerberos_free_data_contents(context, &packet); ZERO_STRUCT(packet); } get_krb5_smb_session_key(context, auth_context, session_key, True); dump_data_pw("SMB session key (from ticket)\n", session_key->data, session_key->length); #if 0 file_save("/tmp/ticket.dat", ticket->data, ticket->length); #endif /* continue when no PAC is retrieved or we couldn't decode the PAC (like accounts that have the UF_NO_AUTH_DATA_REQUIRED flag set, or Kerberos tickets encrypted using a DES key) - Guenther */ got_auth_data = get_auth_data_from_tkt(mem_ctx, &auth_data, tkt); if (!got_auth_data) { DEBUG(3,("ads_verify_ticket: did not retrieve auth data. continuing without PAC\n")); } if (got_auth_data) { pac_ret = decode_pac_data(mem_ctx, &auth_data, context, keyblock, client_principal, authtime, pac_data); if (!NT_STATUS_IS_OK(pac_ret)) { DEBUG(3,("ads_verify_ticket: failed to decode PAC_DATA: %s\n", nt_errstr(pac_ret))); *pac_data = NULL; } data_blob_free(&auth_data); } #if 0 #if defined(HAVE_KRB5_TKT_ENC_PART2) /* MIT */ if (tkt->enc_part2) { file_save("/tmp/authdata.dat", tkt->enc_part2->authorization_data[0]->contents, tkt->enc_part2->authorization_data[0]->length); } #else /* Heimdal */ if (tkt->ticket.authorization_data) { file_save("/tmp/authdata.dat", tkt->ticket.authorization_data->val->ad_data.data, tkt->ticket.authorization_data->val->ad_data.length); } #endif #endif if ((ret = smb_krb5_unparse_name(context, client_principal, principal))) { DEBUG(3,("ads_verify_ticket: smb_krb5_unparse_name failed (%s)\n", error_message(ret))); sret = NT_STATUS_LOGON_FAILURE; goto out; } sret = NT_STATUS_OK; out: TALLOC_FREE(mutex); if (!NT_STATUS_IS_OK(sret)) { data_blob_free(&auth_data); } if (!NT_STATUS_IS_OK(sret)) { data_blob_free(ap_rep); } if (host_princ) { krb5_free_principal(context, host_princ); } if (keyblock) { krb5_free_keyblock(context, keyblock); } if (tkt != NULL) { krb5_free_ticket(context, tkt); } SAFE_FREE(host_princ_s); if (auth_context) { krb5_auth_con_free(context, auth_context); } if (context) { krb5_free_context(context); } return sret; }
static krb5_error_code krb5_get_cred_from_kdc_opt(krb5_context context, krb5_ccache ccache, krb5_creds *in_cred, krb5_creds **out_cred, krb5_creds ***tgts, int kdcopt) { krb5_creds **ret_tgts = NULL; int ntgts = 0; krb5_creds tgt, tgtq, *tgtr = NULL; krb5_error_code retval; krb5_principal int_server = NULL; /* Intermediate server for request */ krb5_principal *tgs_list = NULL; krb5_principal *top_server = NULL; krb5_principal *next_server = NULL; unsigned int nservers = 0; krb5_boolean old_use_conf_ktypes = context->use_conf_ktypes; /* in case we never get a TGT, zero the return */ *tgts = NULL; memset((char *)&tgtq, 0, sizeof(tgtq)); memset((char *)&tgt, 0, sizeof(tgt)); /* * we know that the desired credentials aren't in the cache yet. * * To get them, we first need a tgt for the realm of the server. * first, we see if we have such a TGT in cache. if not, then * we ask the kdc to give us one. if that doesn't work, then * we try to get a tgt for a realm that is closest to the target. * once we have that, then we ask that realm if it can give us * tgt for the target. if not, we do the process over with this * new tgt. */ /* * (the ticket may be issued by some other intermediate * realm's KDC; so we use KRB5_TC_MATCH_SRV_NAMEONLY) */ if ((retval = krb5_copy_principal(context, in_cred->client, &tgtq.client))) goto cleanup; /* get target tgt from cache */ if ((retval = krb5_tgtname(context, krb5_princ_realm(context, in_cred->server), krb5_princ_realm(context, in_cred->client), &int_server))) { goto cleanup; } if ((retval = krb5_copy_principal(context, int_server, &tgtq.server))) { goto cleanup; } /* set endtime to now so krb5_cc_retrieve_cred won't return an expired tik */ if ((retval = krb5_timeofday(context, &(tgtq.times.endtime))) != 0) { goto cleanup; } context->use_conf_ktypes = 1; if ((retval = krb5_cc_retrieve_cred(context, ccache, KRB5_TC_MATCH_SRV_NAMEONLY | KRB5_TC_SUPPORTED_KTYPES | KRB5_TC_MATCH_TIMES, &tgtq, &tgt)) != 0) { if (retval != KRB5_CC_NOTFOUND && retval != KRB5_CC_NOT_KTYPE) { goto cleanup; } /* * Note that we want to request a TGT from our local KDC, even * if we already have a TGT for some intermediate realm. The * reason is that our local KDC may have a shortcut to the * destination realm, and if it does we want to use the * shortcut because it will provide greater security. - bcn */ /* * didn't find it in the cache so it is time to get a local * tgt and walk the realms tree. */ krb5_free_principal(context, int_server); int_server = NULL; if ((retval = krb5_tgtname(context, krb5_princ_realm(context, in_cred->client), krb5_princ_realm(context, in_cred->client), &int_server))) { goto cleanup; } krb5_free_cred_contents(context, &tgtq); memset((char *)&tgtq, 0, sizeof(tgtq)); if ((retval = krb5_copy_principal(context, in_cred->client, &tgtq.client))) goto cleanup; if ((retval = krb5_copy_principal(context, int_server, &tgtq.server))) goto cleanup; if ((retval = krb5_timeofday(context, &(tgtq.times.endtime))) != 0) { goto cleanup; } if ((retval = krb5_cc_retrieve_cred(context, ccache, KRB5_TC_MATCH_SRV_NAMEONLY | KRB5_TC_SUPPORTED_KTYPES | KRB5_TC_MATCH_TIMES, &tgtq, &tgt)) != 0) { goto cleanup; } /* get a list of realms to consult */ if ((retval = krb5_walk_realm_tree(context, krb5_princ_realm(context,in_cred->client), krb5_princ_realm(context,in_cred->server), &tgs_list, KRB5_REALM_BRANCH_CHAR))) { goto cleanup; } for (nservers = 0; tgs_list[nservers]; nservers++) ; /* allocate storage for TGT pointers. */ if (!(ret_tgts = (krb5_creds **) calloc(nservers+1, sizeof(krb5_creds)))) { retval = ENOMEM; goto cleanup; } *tgts = ret_tgts; /* * step one is to take the current tgt and see if there is a tgt for * krbtgt/realmof(target)@realmof(tgt). if not, try to get one with * the tgt. * * if we don't get a tgt for the target, then try to find a tgt as * close to the target realm as possible. at each step if there isn't * a tgt in the cache we have to try and get one with our latest tgt. * once we have a tgt for a closer realm, we go back to step one. * * once we have a tgt for the target, we go try and get credentials. */ for (top_server = tgs_list; top_server < tgs_list + nservers; top_server = next_server) { /* look in cache for a tgt for the destination */ krb5_free_cred_contents(context, &tgtq); memset(&tgtq, 0, sizeof(tgtq)); if ((retval = krb5_copy_principal(context, tgt.client, &tgtq.client))) goto cleanup; krb5_free_principal(context, int_server); int_server = NULL; if ((retval = krb5_tgtname(context, krb5_princ_realm(context, in_cred->server), krb5_princ_realm(context, *top_server), &int_server))) { goto cleanup; } if ((retval = krb5_copy_principal(context, int_server, &tgtq.server))) goto cleanup; if ((retval = krb5_timeofday(context, &(tgtq.times.endtime))) != 0) { goto cleanup; } if ((retval = krb5_cc_retrieve_cred(context, ccache, KRB5_TC_MATCH_SRV_NAMEONLY | KRB5_TC_SUPPORTED_KTYPES | KRB5_TC_MATCH_TIMES, &tgtq, &tgt)) != 0) { if (retval != KRB5_CC_NOTFOUND && retval != KRB5_CC_NOT_KTYPE) { goto cleanup; } /* didn't find it in the cache so try and get one */ /* with current tgt. */ if (!krb5_c_valid_enctype(tgt.keyblock.enctype)) { retval = KRB5_PROG_ETYPE_NOSUPP; goto cleanup; } krb5_free_cred_contents(context, &tgtq); memset(&tgtq, 0, sizeof(tgtq)); tgtq.times = tgt.times; if ((retval = krb5_copy_principal(context, tgt.client, &tgtq.client))) goto cleanup; if ((retval = krb5_copy_principal(context, int_server, &tgtq.server))) goto cleanup; tgtq.is_skey = FALSE; tgtq.ticket_flags = tgt.ticket_flags; retval = krb5_get_cred_via_tkt(context, &tgt, FLAGS2OPTS(tgtq.ticket_flags), tgt.addresses, &tgtq, &tgtr); if (retval) { /* * couldn't get one so now loop backwards through the realms * list and try and get a tgt for a realm as close to the * target as possible. the kdc should give us a tgt for the * closest one it knows about, but not all kdc's do this yet. */ for (next_server = tgs_list + nservers - 1; next_server > top_server; next_server--) { krb5_free_cred_contents(context, &tgtq); memset(&tgtq, 0, sizeof(tgtq)); if ((retval = krb5_copy_principal(context, tgt.client, &tgtq.client))) goto cleanup; krb5_free_principal(context, int_server); int_server = NULL; if ((retval = krb5_tgtname(context, krb5_princ_realm(context, *next_server), krb5_princ_realm(context, *top_server), &int_server))) { goto cleanup; } if ((retval = krb5_copy_principal(context, int_server, &tgtq.server))) goto cleanup; if ((retval = krb5_timeofday(context, &(tgtq.times.endtime))) != 0) { goto cleanup; } if ((retval = krb5_cc_retrieve_cred(context, ccache, KRB5_TC_MATCH_SRV_NAMEONLY | KRB5_TC_SUPPORTED_KTYPES | KRB5_TC_MATCH_TIMES, &tgtq, &tgt)) != 0) { if (retval != KRB5_CC_NOTFOUND) { goto cleanup; } /* not in the cache so try and get one with our current tgt. */ if (!krb5_c_valid_enctype(tgt.keyblock.enctype)) { retval = KRB5_PROG_ETYPE_NOSUPP; goto cleanup; } krb5_free_cred_contents(context, &tgtq); memset(&tgtq, 0, sizeof(tgtq)); tgtq.times = tgt.times; if ((retval = krb5_copy_principal(context, tgt.client, &tgtq.client))) goto cleanup; if ((retval = krb5_copy_principal(context, int_server, &tgtq.server))) goto cleanup; tgtq.is_skey = FALSE; tgtq.ticket_flags = tgt.ticket_flags; retval = krb5_get_cred_via_tkt(context, &tgt, FLAGS2OPTS(tgtq.ticket_flags), tgt.addresses, &tgtq, &tgtr); if (retval) continue; /* save tgt in return array */ if ((retval = krb5_copy_creds(context, tgtr, &ret_tgts[ntgts]))) { goto cleanup; } krb5_free_creds(context, tgtr); tgtr = NULL; tgt = *ret_tgts[ntgts++]; } /* got one as close as possible, now start all over */ break; } if (next_server == top_server) { goto cleanup; } continue; } /* * Got a tgt. If it is for the target realm we can go try for the * credentials. If it is not for the target realm, then make sure it * is in the realms hierarchy and if so, save it and start the loop * over from there. Note that we only need to compare the instance * names since that is the target realm of the tgt. */ for (next_server = top_server; *next_server; next_server++) { krb5_data *realm_1 = krb5_princ_component(context, next_server[0], 1); krb5_data *realm_2 = krb5_princ_component(context, tgtr->server, 1); if (realm_1 != NULL && realm_2 != NULL && realm_1->length == realm_2->length && !memcmp(realm_1->data, realm_2->data, realm_1->length)) { break; } } if (!next_server) { retval = KRB5_KDCREP_MODIFIED; goto cleanup; } if ((retval = krb5_copy_creds(context, tgtr, &ret_tgts[ntgts]))) { goto cleanup; } krb5_free_creds(context, tgtr); tgtr = NULL; tgt = *ret_tgts[ntgts++]; /* we're done if it is the target */ if (!*next_server++) break; } } } /* got/finally have tgt! try for the creds */ if (!krb5_c_valid_enctype(tgt.keyblock.enctype)) { retval = KRB5_PROG_ETYPE_NOSUPP; goto cleanup; } context->use_conf_ktypes = old_use_conf_ktypes; retval = krb5_get_cred_via_tkt(context, &tgt, FLAGS2OPTS(tgt.ticket_flags) | kdcopt | (in_cred->second_ticket.length ? KDC_OPT_ENC_TKT_IN_SKEY : 0), tgt.addresses, in_cred, out_cred); /* cleanup and return */ cleanup: if (tgtr) krb5_free_creds(context, tgtr); if(tgs_list) krb5_free_realm_tree(context, tgs_list); krb5_free_cred_contents(context, &tgtq); if (int_server) krb5_free_principal(context, int_server); if (ntgts == 0) { *tgts = NULL; if (ret_tgts) free(ret_tgts); krb5_free_cred_contents(context, &tgt); } context->use_conf_ktypes = old_use_conf_ktypes; return(retval); }
int do_krb5_login (int infd, struct auth_data *ap, const char **err_msg) { krb5_auth_context auth_ctx = NULL; krb5_error_code status; krb5_data inbuf; krb5_data version; krb5_authenticator *authenticator; krb5_rcache rcache; krb5_keyblock *key; krb5_ticket *ticket; struct sockaddr_in laddr; int len; struct passwd *pwd; char *name; if (status = krb5_init_context (&ap->context)) { syslog (LOG_ERR, "Error initializing krb5: %s", error_message (status)); return status; } if ((status = krb5_auth_con_init (ap->context, &auth_ctx)) || (status = krb5_auth_con_genaddrs (ap->context, auth_ctx, infd, KRB5_AUTH_CONTEXT_GENERATE_REMOTE_FULL_ADDR)) || (status = krb5_auth_con_getrcache (ap->context, auth_ctx, &rcache))) return status; if (!rcache) { krb5_principal server; status = krb5_sname_to_principal (ap->context, 0, 0, KRB5_NT_SRV_HST, &server); if (status) return status; status = krb5_get_server_rcache (ap->context, krb5_princ_component (ap->context, server, 0), &rcache); krb5_free_principal (ap->context, server); if (status) return status; status = krb5_auth_con_setrcache (ap->context, auth_ctx, rcache); if (status) return status; } len = sizeof (laddr); if (getsockname (infd, (struct sockaddr *) &laddr, &len)) return errno; status = krb5_recvauth (ap->context, &auth_ctx, &infd, NULL, 0, 0, ap->keytab, &ticket); if (status) return status; if ((status = krb5_auth_con_getauthenticator (ap->context, auth_ctx, &authenticator))) return status; getstr (infd, &ap->lusername, NULL); getstr (infd, &ap->term, "TERM="); pwd = getpwnam (ap->lusername); if (pwd == NULL) { *err_msg = "getpwnam failed"; syslog (LOG_ERR, "getpwnam failed: %m"); return 1; } getstr (infd, &ap->rusername, NULL); if ((status = krb5_copy_principal (ap->context, ticket->enc_part2->client, &ap->client))) return status; /*OK:: */ if (ap->client && !krb5_kuserok (ap->context, ap->client, ap->lusername)) return 1; krb5_unparse_name (ap->context, ap->client, &name); syslog (LOG_INFO | LOG_AUTH, "%sKerberos V login from %s on %s\n", (pwd->pw_uid == 0) ? "ROOT " : "", name, ap->hostname); free (name); return 0; }
krb5_error_code KRB5_CALLCONV krb5_524_conv_principal(krb5_context context, krb5_const_principal princ, char *name, char *inst, char *realm) { const struct krb_convert *p; const krb5_data *compo; char *c, *tmp_realm, *tmp_prealm; unsigned int tmp_realm_len; int retval; *name = *inst = '\0'; switch (krb5_princ_size(context, princ)) { case 2: /* Check if this principal is listed in the table */ compo = krb5_princ_component(context, princ, 0); p = sconv_list; while (p->v4_str) { if (p->len == compo->length && memcmp(p->v5_str, compo->data, compo->length) == 0) { /* * It is, so set the new name now, and chop off * instance's domain name if requested. */ if (strlcpy(name, p->v4_str, ANAME_SZ) >= ANAME_SZ) return KRB5_INVALID_PRINCIPAL; if (p->flags & DO_REALM_CONVERSION) { compo = krb5_princ_component(context, princ, 1); c = strnchr(compo->data, '.', compo->length); if (!c || (c - compo->data) >= INST_SZ - 1) return KRB5_INVALID_PRINCIPAL; memcpy(inst, compo->data, (size_t) (c - compo->data)); inst[c - compo->data] = '\0'; } break; } p++; } /* If inst isn't set, the service isn't listed in the table, */ /* so just copy it. */ if (*inst == '\0') { compo = krb5_princ_component(context, princ, 1); if (compo->length >= INST_SZ - 1) return KRB5_INVALID_PRINCIPAL; memcpy(inst, compo->data, compo->length); inst[compo->length] = '\0'; } /* fall through */ case 1: /* name may have been set above; otherwise, just copy it */ if (*name == '\0') { compo = krb5_princ_component(context, princ, 0); if (compo->length >= ANAME_SZ) return KRB5_INVALID_PRINCIPAL; memcpy(name, compo->data, compo->length); name[compo->length] = '\0'; } break; default: return KRB5_INVALID_PRINCIPAL; } compo = krb5_princ_realm(context, princ); tmp_prealm = malloc(compo->length + 1); if (tmp_prealm == NULL) return ENOMEM; strncpy(tmp_prealm, compo->data, compo->length); tmp_prealm[compo->length] = '\0'; /* Ask for v4_realm corresponding to krb5 principal realm from krb5.conf realms stanza */ if (context->profile == 0) return KRB5_CONFIG_CANTOPEN; retval = profile_get_string(context->profile, "realms", tmp_prealm, "v4_realm", 0, &tmp_realm); free(tmp_prealm); if (retval) { return retval; } else { if (tmp_realm == 0) { if (compo->length > REALM_SZ - 1) return KRB5_INVALID_PRINCIPAL; strncpy(realm, compo->data, compo->length); realm[compo->length] = '\0'; } else { tmp_realm_len = strlen(tmp_realm); if (tmp_realm_len > REALM_SZ - 1) return KRB5_INVALID_PRINCIPAL; strncpy(realm, tmp_realm, tmp_realm_len); realm[tmp_realm_len] = '\0'; profile_release_string(tmp_realm); } } return 0; }
/* * find_nxt_kdc() * * A NXT_TGT gotten from an intermediate KDC might actually be a * referral. Search KDC_LIST forward starting from CUR_KDC, looking * for the KDC with the same remote realm as NXT_TGT. If we don't * find it, the intermediate KDC is leading us off the transit path. * * Match on CUR_KDC's remote realm, not local realm, because, among * other reasons, we can get a referral to the final realm; e.g., * given * * KDC_LIST == { krbtgt/R1@R1, krbtgt/R2@R1, krbtgt/R3@R2, * krbtgt/R4@R3, NULL } * CUR_TGT->SERVER == krbtgt/R2@R1 * NXT_TGT->SERVER == krbtgt/R4@R2 * * i.e., we got a ticket issued by R2 with remote realm R4, we want to * find krbtgt/R4@R3, not krbtgt/R3@R2, even though we have no TGT * with R3 as its local realm. * * Set up for next iteration of do_traversal() loop by pointing * NXT_KDC to one entry forward of the match. */ static krb5_error_code find_nxt_kdc(struct tr_state *ts) { krb5_data *r1, *r2; krb5_principal *kdcptr; TR_DBG(ts, "find_nxt_kdc"); /* * Solaris Kerberos: * The following assertion is not be true for the case when * ts->nxt points to a cached ticket and not to a freshly * fetched TGT in ts->kdc_tgts. See changes in try_kdc() */ /* assert(ts->nxt_tgt == ts->kdc_tgts[ts->ntgts-1]); */ if (krb5_princ_size(ts->ctx, ts->nxt_tgt->server) != 2) { /* Solaris Kerberos */ char *s_name = NULL; int err = krb5_unparse_name(ts->ctx, ts->nxt_tgt->server, &s_name); if (!err) { krb5_set_error_message(ts->ctx, KRB5_KDCREP_MODIFIED, dgettext(TEXT_DOMAIN, "KDC reply did not match expectations: server '%s' principal size should be 2"), s_name); krb5_free_unparsed_name(ts->ctx, s_name); } else krb5_set_error_message(ts->ctx, KRB5_KDCREP_MODIFIED, dgettext(TEXT_DOMAIN, "KDC reply did not match expectations: server principal size should be 2")); return KRB5_KDCREP_MODIFIED; } r1 = krb5_princ_component(ts->ctx, ts->nxt_tgt->server, 1); for (kdcptr = ts->cur_kdc + 1; *kdcptr != NULL; kdcptr++) { r2 = krb5_princ_component(ts->ctx, *kdcptr, 1); if (r1 != NULL && r2 != NULL && r1->length == r2->length && !memcmp(r1->data, r2->data, r1->length)) { break; } } if (*kdcptr == NULL) { /* * Not found; we probably got an unexpected realm referral. * Don't touch NXT_KDC, thus allowing next_closest_tgt() to * continue looping backwards. */ /* * Solaris Kerberos: * Only free the allocated creds if they are in kdc_tgts. If they * are in cc_tgts no freeing is necessary. */ if (ts->ntgts > 0 && ts->nxt_tgt == ts->kdc_tgts[ts->ntgts-1]) { /* Punt NXT_TGT from KDC_TGTS if bogus. */ krb5_free_creds(ts->ctx, ts->kdc_tgts[--ts->ntgts]); ts->kdc_tgts[ts->ntgts] = NULL; } TR_DBG_RET(ts, "find_nxt_kdc", KRB5_KDCREP_MODIFIED); krb5_set_error_message(ts->ctx, KRB5_KDCREP_MODIFIED, dgettext(TEXT_DOMAIN, "KDC reply did not match expectation: KDC not found. Probably got an unexpected realm referral")); return KRB5_KDCREP_MODIFIED; } ts->nxt_kdc = kdcptr; TR_DBG_RET(ts, "find_nxt_kdc", 0); return 0; }
krb5_error_code sam_get_db_entry(krb5_context context, krb5_principal client, int *sam_type, struct _krb5_db_entry_new **db_entry) { struct _krb5_db_entry_new *assoc = NULL; krb5_principal newp = NULL; int probeslot; void *ptr = NULL; krb5_error_code retval; if (db_entry) *db_entry = NULL; retval = krb5_copy_principal(context, client, &newp); if (retval) { com_err("krb5kdc", retval, "copying client name for preauth probe"); return retval; } probeslot = krb5_princ_size(context, newp)++; ptr = realloc(krb5_princ_name(context, newp), krb5_princ_size(context, newp) * sizeof(krb5_data)); if (ptr == NULL) { retval = ENOMEM; goto cleanup; } krb5_princ_name(context, newp) = ptr; for(sam_ptr = sam_inst_map; sam_ptr->name; sam_ptr++) { if (*sam_type && *sam_type != sam_ptr->sam_type) continue; krb5_princ_component(context,newp,probeslot)->data = sam_ptr->name; krb5_princ_component(context,newp,probeslot)->length = strlen(sam_ptr->name); retval = krb5_db_get_principal(context, newp, 0, &assoc); if (!retval) break; } cleanup: if (ptr) { krb5_princ_component(context,newp,probeslot)->data = 0; krb5_princ_component(context,newp,probeslot)->length = 0; krb5_free_principal(context, newp); } if (probeslot) krb5_princ_size(context, newp)--; if (retval) return retval; if (sam_ptr->sam_type) { /* Found entry of type sam_ptr->sam_type */ if (sam_type) *sam_type = sam_ptr->sam_type; if (db_entry) *db_entry = assoc; else krb5_db_free_principal(context, assoc); return 0; } else { return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; } }
static krb5_int32 prep_reprocess_req(krb5_kdc_req *request, krb5_principal *krbtgt_princ) { krb5_error_code retval = KRB5KRB_AP_ERR_BADMATCH; size_t len = 0; char **realms, **cpp, *temp_buf=NULL; krb5_data *comp1 = NULL, *comp2 = NULL; char *comp1_str = NULL; /* By now we know that server principal name is unknown. * If CANONICALIZE flag is set in the request * If req is not U2U authn. req * the requested server princ. has exactly two components * either * the name type is NT-SRV-HST * or name type is NT-UNKNOWN and * the 1st component is listed in conf file under host_based_services * the 1st component is not in a list in conf under "no_host_referral" * the 2d component looks like fully-qualified domain name (FQDN) * If all of these conditions are satisfied - try mapping the FQDN and * re-process the request as if client had asked for cross-realm TGT. */ if (isflagset(request->kdc_options, KDC_OPT_CANONICALIZE) && !isflagset(request->kdc_options, KDC_OPT_ENC_TKT_IN_SKEY) && krb5_princ_size(kdc_context, request->server) == 2) { comp1 = krb5_princ_component(kdc_context, request->server, 0); comp2 = krb5_princ_component(kdc_context, request->server, 1); comp1_str = calloc(1,comp1->length+1); if (!comp1_str) { retval = ENOMEM; goto cleanup; } strlcpy(comp1_str,comp1->data,comp1->length+1); if ((krb5_princ_type(kdc_context, request->server) == KRB5_NT_SRV_HST || (krb5_princ_type(kdc_context, request->server) == KRB5_NT_UNKNOWN && kdc_active_realm->realm_host_based_services != NULL && (krb5_match_config_pattern(kdc_active_realm->realm_host_based_services, comp1_str) == TRUE || krb5_match_config_pattern(kdc_active_realm->realm_host_based_services, KRB5_CONF_ASTERISK) == TRUE))) && (kdc_active_realm->realm_no_host_referral == NULL || (krb5_match_config_pattern(kdc_active_realm->realm_no_host_referral, KRB5_CONF_ASTERISK) == FALSE && krb5_match_config_pattern(kdc_active_realm->realm_no_host_referral, comp1_str) == FALSE))) { for (len=0; len < comp2->length; len++) { if (comp2->data[len] == '.') break; } if (len == comp2->length) goto cleanup; temp_buf = calloc(1, comp2->length+1); if (!temp_buf){ retval = ENOMEM; goto cleanup; } strlcpy(temp_buf, comp2->data,comp2->length+1); retval = krb5int_get_domain_realm_mapping(kdc_context, temp_buf, &realms); free(temp_buf); if (retval) { /* no match found */ kdc_err(kdc_context, retval, 0); goto cleanup; } if (realms == 0) { retval = KRB5KRB_AP_ERR_BADMATCH; goto cleanup; } if (realms[0] == 0) { free(realms); retval = KRB5KRB_AP_ERR_BADMATCH; goto cleanup; } /* Modify request. * Construct cross-realm tgt : krbtgt/REMOTE_REALM@LOCAL_REALM * and use it as a principal in this req. */ retval = krb5_build_principal(kdc_context, krbtgt_princ, (*request->server).realm.length, (*request->server).realm.data, "krbtgt", realms[0], (char *)0); for (cpp = realms; *cpp; cpp++) free(*cpp); } } cleanup: free(comp1_str); return retval; }
/* * The request seems to be for a ticket-granting service somewhere else, * but we don't have a ticket for the final TGS. Try to give the requestor * some intermediate realm. */ static krb5_error_code find_alternate_tgs(krb5_kdc_req *request, krb5_db_entry **server_ptr) { krb5_error_code retval; krb5_principal *plist = NULL, *pl2, tmpprinc; krb5_data tmp; krb5_db_entry *server = NULL; *server_ptr = NULL; /* * Call to krb5_princ_component is normally not safe but is so * here only because find_alternate_tgs() is only called from * somewhere that has already checked the number of components in * the principal. */ if ((retval = krb5_walk_realm_tree(kdc_context, krb5_princ_realm(kdc_context, request->server), krb5_princ_component(kdc_context, request->server, 1), &plist, KRB5_REALM_BRANCH_CHAR))) return retval; /* move to the end */ for (pl2 = plist; *pl2; pl2++); /* the first entry in this array is for krbtgt/local@local, so we ignore it */ while (--pl2 > plist) { tmp = *krb5_princ_realm(kdc_context, *pl2); krb5_princ_set_realm(kdc_context, *pl2, krb5_princ_realm(kdc_context, tgs_server)); retval = krb5_db_get_principal(kdc_context, *pl2, 0, &server); krb5_princ_set_realm(kdc_context, *pl2, &tmp); if (retval == KRB5_KDB_NOENTRY) continue; else if (retval) goto cleanup; /* Found it. */ tmp = *krb5_princ_realm(kdc_context, *pl2); krb5_princ_set_realm(kdc_context, *pl2, krb5_princ_realm(kdc_context, tgs_server)); retval = krb5_copy_principal(kdc_context, *pl2, &tmpprinc); if (retval) goto cleanup; krb5_princ_set_realm(kdc_context, *pl2, &tmp); krb5_free_principal(kdc_context, request->server); request->server = tmpprinc; log_tgs_alt_tgt(request->server); *server_ptr = server; server = NULL; goto cleanup; } retval = KRB5_KDB_NOENTRY; cleanup: krb5_free_realm_tree(kdc_context, plist); krb5_db_free_principal(kdc_context, server); return retval; }
/* * The request seems to be for a ticket-granting service somewhere else, * but we don't have a ticket for the final TGS. Try to give the requestor * some intermediate realm. */ static void find_alternate_tgs(krb5_kdc_req *request, krb5_db_entry *server, krb5_boolean *more, int *nprincs) { krb5_error_code retval; krb5_principal *plist, *pl2; krb5_data tmp; *nprincs = 0; *more = FALSE; /* * Call to krb5_princ_component is normally not safe but is so * here only because find_alternate_tgs() is only called from * somewhere that has already checked the number of components in * the principal. */ if ((retval = krb5_walk_realm_tree(kdc_context, krb5_princ_realm(kdc_context, request->server), krb5_princ_component(kdc_context, request->server, 1), &plist, KRB5_REALM_BRANCH_CHAR))) return; /* move to the end */ for (pl2 = plist; *pl2; pl2++); /* the first entry in this array is for krbtgt/local@local, so we ignore it */ while (--pl2 > plist) { *nprincs = 1; tmp = *krb5_princ_realm(kdc_context, *pl2); krb5_princ_set_realm(kdc_context, *pl2, krb5_princ_realm(kdc_context, tgs_server)); retval = get_principal(kdc_context, *pl2, server, nprincs, more); krb5_princ_set_realm(kdc_context, *pl2, &tmp); if (retval) { *nprincs = 0; *more = FALSE; krb5_free_realm_tree(kdc_context, plist); return; } if (*more) { krb5_db_free_principal(kdc_context, server, *nprincs); continue; } else if (*nprincs == 1) { /* Found it! */ krb5_principal tmpprinc; tmp = *krb5_princ_realm(kdc_context, *pl2); krb5_princ_set_realm(kdc_context, *pl2, krb5_princ_realm(kdc_context, tgs_server)); if ((retval = krb5_copy_principal(kdc_context, *pl2, &tmpprinc))) { krb5_db_free_principal(kdc_context, server, *nprincs); krb5_princ_set_realm(kdc_context, *pl2, &tmp); continue; } krb5_princ_set_realm(kdc_context, *pl2, &tmp); krb5_free_principal(kdc_context, request->server); request->server = tmpprinc; log_tgs_alt_tgt(request->server); krb5_free_realm_tree(kdc_context, plist); return; } krb5_db_free_principal(kdc_context, server, *nprincs); continue; } *nprincs = 0; *more = FALSE; krb5_free_realm_tree(kdc_context, plist); return; }
static krb5_error_code rd_req_decoded_opt(krb5_context context, krb5_auth_context *auth_context, const krb5_ap_req *req, krb5_const_principal server, krb5_keytab keytab, krb5_flags *ap_req_options, krb5_ticket **ticket, int check_valid_flag) { krb5_error_code retval = 0; krb5_enctype *desired_etypes = NULL; int desired_etypes_len = 0; int rfc4537_etypes_len = 0; krb5_enctype *permitted_etypes = NULL; int permitted_etypes_len = 0; krb5_keyblock decrypt_key; decrypt_key.enctype = ENCTYPE_NULL; decrypt_key.contents = NULL; req->ticket->enc_part2 = NULL; /* if (req->ap_options & AP_OPTS_USE_SESSION_KEY) do we need special processing here ? */ /* decrypt the ticket */ if ((*auth_context)->key) { /* User to User authentication */ if ((retval = krb5_decrypt_tkt_part(context, &(*auth_context)->key->keyblock, req->ticket))) goto cleanup; if (check_valid_flag) { decrypt_key = (*auth_context)->key->keyblock; (*auth_context)->key->keyblock.contents = NULL; } krb5_k_free_key(context, (*auth_context)->key); (*auth_context)->key = NULL; } else { retval = decrypt_ticket(context, req, server, keytab, check_valid_flag ? &decrypt_key : NULL); if (retval) goto cleanup; } TRACE_RD_REQ_TICKET(context, req->ticket->enc_part2->client, req->ticket->server, req->ticket->enc_part2->session); /* XXX this is an evil hack. check_valid_flag is set iff the call is not from inside the kdc. we can use this to determine which key usage to use */ #ifndef LEAN_CLIENT if ((retval = decrypt_authenticator(context, req, &((*auth_context)->authentp), check_valid_flag))) goto cleanup; #endif if (!krb5_principal_compare(context, (*auth_context)->authentp->client, req->ticket->enc_part2->client)) { retval = KRB5KRB_AP_ERR_BADMATCH; goto cleanup; } if ((*auth_context)->remote_addr && !krb5_address_search(context, (*auth_context)->remote_addr, req->ticket->enc_part2->caddrs)) { retval = KRB5KRB_AP_ERR_BADADDR; goto cleanup; } if (!server) { server = req->ticket->server; } /* Get an rcache if necessary. */ if (((*auth_context)->rcache == NULL) && ((*auth_context)->auth_context_flags & KRB5_AUTH_CONTEXT_DO_TIME) && server) { if ((retval = krb5_get_server_rcache(context, krb5_princ_component(context,server,0), &(*auth_context)->rcache))) goto cleanup; } /* okay, now check cross-realm policy */ #if defined(_SINGLE_HOP_ONLY) /* Single hop cross-realm tickets only */ { krb5_transited *trans = &(req->ticket->enc_part2->transited); /* If the transited list is empty, then we have at most one hop */ if (trans->tr_contents.length > 0 && trans->tr_contents.data[0]) retval = KRB5KRB_AP_ERR_ILL_CR_TKT; } #elif defined(_NO_CROSS_REALM) /* No cross-realm tickets */ { char * lrealm; krb5_data * realm; krb5_transited * trans; realm = krb5_princ_realm(context, req->ticket->enc_part2->client); trans = &(req->ticket->enc_part2->transited); /* * If the transited list is empty, then we have at most one hop * So we also have to check that the client's realm is the local one */ krb5_get_default_realm(context, &lrealm); if ((trans->tr_contents.length > 0 && trans->tr_contents.data[0]) || !data_eq_string(*realm, lrealm)) { retval = KRB5KRB_AP_ERR_ILL_CR_TKT; } free(lrealm); } #else /* Hierarchical Cross-Realm */ { krb5_data * realm; krb5_transited * trans; realm = krb5_princ_realm(context, req->ticket->enc_part2->client); trans = &(req->ticket->enc_part2->transited); /* * If the transited list is not empty, then check that all realms * transited are within the hierarchy between the client's realm * and the local realm. */ if (trans->tr_contents.length > 0 && trans->tr_contents.data[0]) { retval = krb5_check_transited_list(context, &(trans->tr_contents), realm, krb5_princ_realm (context,server)); } } #endif if (retval) goto cleanup; /* only check rcache if sender has provided one---some services may not be able to use replay caches (such as datagram servers) */ if ((*auth_context)->rcache) { krb5_donot_replay rep; krb5_tkt_authent tktauthent; tktauthent.ticket = req->ticket; tktauthent.authenticator = (*auth_context)->authentp; if (!(retval = krb5_auth_to_rep(context, &tktauthent, &rep))) { retval = krb5_rc_hash_message(context, &req->authenticator.ciphertext, &rep.msghash); if (!retval) { retval = krb5_rc_store(context, (*auth_context)->rcache, &rep); free(rep.msghash); } free(rep.server); free(rep.client); } if (retval) goto cleanup; } retval = krb5int_validate_times(context, &req->ticket->enc_part2->times); if (retval != 0) goto cleanup; if ((retval = krb5_check_clockskew(context, (*auth_context)->authentp->ctime))) goto cleanup; if (check_valid_flag) { if (req->ticket->enc_part2->flags & TKT_FLG_INVALID) { retval = KRB5KRB_AP_ERR_TKT_INVALID; goto cleanup; } if ((retval = krb5_authdata_context_init(context, &(*auth_context)->ad_context))) goto cleanup; if ((retval = krb5int_authdata_verify(context, (*auth_context)->ad_context, AD_USAGE_MASK, auth_context, &decrypt_key, req))) goto cleanup; } /* read RFC 4537 etype list from sender */ retval = decode_etype_list(context, (*auth_context)->authentp, &desired_etypes, &rfc4537_etypes_len); if (retval != 0) goto cleanup; if (desired_etypes == NULL) desired_etypes = (krb5_enctype *)calloc(4, sizeof(krb5_enctype)); else desired_etypes = (krb5_enctype *)realloc(desired_etypes, (rfc4537_etypes_len + 4) * sizeof(krb5_enctype)); if (desired_etypes == NULL) { retval = ENOMEM; goto cleanup; } desired_etypes_len = rfc4537_etypes_len; /* * RFC 4537: * * If the EtypeList is present and the server prefers an enctype from * the client's enctype list over that of the AP-REQ authenticator * subkey (if that is present) or the service ticket session key, the * server MUST create a subkey using that enctype. This negotiated * subkey is sent in the subkey field of AP-REP message, and it is then * used as the protocol key or base key [RFC3961] for subsequent * communication. * * If the enctype of the ticket session key is included in the enctype * list sent by the client, it SHOULD be the last on the list; * otherwise, this enctype MUST NOT be negotiated if it was not included * in the list. * * The second paragraph does appear to contradict the first with respect * to whether it is legal to negotiate the ticket session key type if it * is absent in the EtypeList. A literal reading suggests that we can use * the AP-REQ subkey enctype. Also a client has no way of distinguishing * a server that does not RFC 4537 from one that has chosen the same * enctype as the ticket session key for the acceptor subkey, surely. */ if ((*auth_context)->authentp->subkey != NULL) { desired_etypes[desired_etypes_len++] = (*auth_context)->authentp->subkey->enctype; } desired_etypes[desired_etypes_len++] = req->ticket->enc_part2->session->enctype; desired_etypes[desired_etypes_len] = ENCTYPE_NULL; if (((*auth_context)->auth_context_flags & KRB5_AUTH_CONTEXT_PERMIT_ALL) == 0) { if ((*auth_context)->permitted_etypes != NULL) { permitted_etypes = (*auth_context)->permitted_etypes; } else { retval = krb5_get_permitted_enctypes(context, &permitted_etypes); if (retval != 0) goto cleanup; } permitted_etypes_len = krb5int_count_etypes(permitted_etypes); } else { permitted_etypes = NULL; permitted_etypes_len = 0; } /* check if the various etypes are permitted */ retval = negotiate_etype(context, desired_etypes, desired_etypes_len, rfc4537_etypes_len, permitted_etypes, permitted_etypes_len, &(*auth_context)->negotiated_etype); if (retval != 0) goto cleanup; TRACE_RD_REQ_NEGOTIATED_ETYPE(context, (*auth_context)->negotiated_etype); assert((*auth_context)->negotiated_etype != ENCTYPE_NULL); (*auth_context)->remote_seq_number = (*auth_context)->authentp->seq_number; if ((*auth_context)->authentp->subkey) { TRACE_RD_REQ_SUBKEY(context, (*auth_context)->authentp->subkey); if ((retval = krb5_k_create_key(context, (*auth_context)->authentp->subkey, &((*auth_context)->recv_subkey)))) goto cleanup; retval = krb5_k_create_key(context, (*auth_context)->authentp->subkey, &((*auth_context)->send_subkey)); if (retval) { krb5_k_free_key(context, (*auth_context)->recv_subkey); (*auth_context)->recv_subkey = NULL; goto cleanup; } } else { (*auth_context)->recv_subkey = 0; (*auth_context)->send_subkey = 0; } if ((retval = krb5_k_create_key(context, req->ticket->enc_part2->session, &((*auth_context)->key)))) goto cleanup; debug_log_authz_data("ticket", req->ticket->enc_part2->authorization_data); /* * If not AP_OPTS_MUTUAL_REQUIRED then and sequence numbers are used * then the default sequence number is the one's complement of the * sequence number sent ot us. */ if ((!(req->ap_options & AP_OPTS_MUTUAL_REQUIRED)) && (*auth_context)->remote_seq_number) { (*auth_context)->local_seq_number ^= (*auth_context)->remote_seq_number; } if (ticket) if ((retval = krb5_copy_ticket(context, req->ticket, ticket))) goto cleanup; if (ap_req_options) { *ap_req_options = req->ap_options & AP_OPTS_WIRE_MASK; if (rfc4537_etypes_len != 0) *ap_req_options |= AP_OPTS_ETYPE_NEGOTIATION; if ((*auth_context)->negotiated_etype != krb5_k_key_enctype(context, (*auth_context)->key)) *ap_req_options |= AP_OPTS_USE_SUBKEY; } retval = 0; cleanup: if (desired_etypes != NULL) free(desired_etypes); if (permitted_etypes != NULL && permitted_etypes != (*auth_context)->permitted_etypes) free(permitted_etypes); if (retval) { /* only free if we're erroring out...otherwise some applications will need the output. */ if (req->ticket->enc_part2) krb5_free_enc_tkt_part(context, req->ticket->enc_part2); req->ticket->enc_part2 = NULL; } if (check_valid_flag) krb5_free_keyblock_contents(context, &decrypt_key); return retval; }
/*ARGSUSED*/ krb5_error_code process_tgs_req(krb5_data *pkt, const krb5_fulladdr *from, krb5_data **response) { krb5_keyblock * subkey = 0; krb5_kdc_req *request = 0; krb5_db_entry server; krb5_kdc_rep reply; krb5_enc_kdc_rep_part reply_encpart; krb5_ticket ticket_reply, *header_ticket = 0; int st_idx = 0; krb5_enc_tkt_part enc_tkt_reply; krb5_transited enc_tkt_transited; int newtransited = 0; krb5_error_code retval = 0; krb5_keyblock encrypting_key; int nprincs = 0; krb5_boolean more; krb5_timestamp kdc_time, authtime=0; krb5_keyblock session_key; krb5_timestamp until, rtime; krb5_keyblock *reply_key = NULL; krb5_keyblock *mkey_ptr; krb5_key_data *server_key; char *cname = 0, *sname = 0, *altcname = 0; krb5_last_req_entry *nolrarray[2], nolrentry; krb5_enctype useenctype; int errcode, errcode2; register int i; int firstpass = 1; const char *status = 0; krb5_enc_tkt_part *header_enc_tkt = NULL; /* ticket granting or evidence ticket */ krb5_db_entry client, krbtgt; int c_nprincs = 0, k_nprincs = 0; krb5_pa_s4u_x509_user *s4u_x509_user = NULL; /* protocol transition request */ krb5_authdata **kdc_issued_auth_data = NULL; /* auth data issued by KDC */ unsigned int c_flags = 0, s_flags = 0; /* client/server KDB flags */ char *s4u_name = NULL; krb5_boolean is_referral, db_ref_done = FALSE; const char *emsg = NULL; krb5_data *tgs_1 =NULL, *server_1 = NULL; krb5_principal krbtgt_princ; krb5_kvno ticket_kvno = 0; struct kdc_request_state *state = NULL; krb5_pa_data *pa_tgs_req; /*points into request*/ krb5_data scratch; session_key.contents = NULL; retval = decode_krb5_tgs_req(pkt, &request); if (retval) return retval; /* * setup_server_realm() sets up the global realm-specific data pointer. */ if ((retval = setup_server_realm(request->server))) { krb5_free_kdc_req(kdc_context, request); return retval; } errcode = kdc_process_tgs_req(request, from, pkt, &header_ticket, &krbtgt, &k_nprincs, &subkey, &pa_tgs_req); if (header_ticket && header_ticket->enc_part2 && (errcode2 = krb5_unparse_name(kdc_context, header_ticket->enc_part2->client, &cname))) { status = "UNPARSING CLIENT"; errcode = errcode2; goto cleanup; } limit_string(cname); if (errcode) { status = "PROCESS_TGS"; goto cleanup; } if (!header_ticket) { errcode = KRB5_NO_TKT_SUPPLIED; /* XXX? */ status="UNEXPECTED NULL in header_ticket"; goto cleanup; } errcode = kdc_make_rstate(&state); if (errcode !=0) { status = "making state"; goto cleanup; } scratch.length = pa_tgs_req->length; scratch.data = (char *) pa_tgs_req->contents; errcode = kdc_find_fast(&request, &scratch, subkey, header_ticket->enc_part2->session, state); if (errcode !=0) { status = "kdc_find_fast"; goto cleanup; } /* * Pointer to the encrypted part of the header ticket, which may be * replaced to point to the encrypted part of the evidence ticket * if constrained delegation is used. This simplifies the number of * special cases for constrained delegation. */ header_enc_tkt = header_ticket->enc_part2; /* * We've already dealt with the AP_REQ authentication, so we can * use header_ticket freely. The encrypted part (if any) has been * decrypted with the session key. */ /* XXX make sure server here has the proper realm...taken from AP_REQ header? */ if (isflagset(request->kdc_options, KDC_OPT_CANONICALIZE)) { setflag(c_flags, KRB5_KDB_FLAG_CANONICALIZE); setflag(s_flags, KRB5_KDB_FLAG_CANONICALIZE); } db_ref_done = FALSE; ref_tgt_again: nprincs = 1; if ((errcode = krb5_unparse_name(kdc_context, request->server, &sname))) { status = "UNPARSING SERVER"; goto cleanup; } limit_string(sname); errcode = krb5_db_get_principal_ext(kdc_context, request->server, s_flags, &server, &nprincs, &more); if (errcode) { status = "LOOKING_UP_SERVER"; nprincs = 0; goto cleanup; } tgt_again: if (more) { status = "NON_UNIQUE_PRINCIPAL"; errcode = KRB5KDC_ERR_PRINCIPAL_NOT_UNIQUE; goto cleanup; } else if (nprincs != 1) { /* * might be a request for a TGT for some other realm; we * should do our best to find such a TGS in this db */ if (firstpass ) { if ( krb5_is_tgs_principal(request->server) == TRUE) { /* Principal is a name of krb ticket service */ if (krb5_princ_size(kdc_context, request->server) == 2) { server_1 = krb5_princ_component(kdc_context, request->server, 1); tgs_1 = krb5_princ_component(kdc_context, tgs_server, 1); if (!tgs_1 || !data_eq(*server_1, *tgs_1)) { krb5_db_free_principal(kdc_context, &server, nprincs); find_alternate_tgs(request, &server, &more, &nprincs); firstpass = 0; goto tgt_again; } } krb5_db_free_principal(kdc_context, &server, nprincs); status = "UNKNOWN_SERVER"; errcode = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; goto cleanup; } else if ( db_ref_done == FALSE) { retval = prep_reprocess_req(request, &krbtgt_princ); if (!retval) { krb5_free_principal(kdc_context, request->server); retval = krb5_copy_principal(kdc_context, krbtgt_princ, &(request->server)); if (!retval) { db_ref_done = TRUE; if (sname != NULL) free(sname); goto ref_tgt_again; } } } } krb5_db_free_principal(kdc_context, &server, nprincs); status = "UNKNOWN_SERVER"; errcode = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; goto cleanup; } if ((errcode = krb5_timeofday(kdc_context, &kdc_time))) { status = "TIME_OF_DAY"; goto cleanup; } if ((retval = validate_tgs_request(request, server, header_ticket, kdc_time, &status))) { if (!status) status = "UNKNOWN_REASON"; errcode = retval + ERROR_TABLE_BASE_krb5; goto cleanup; } if (!is_local_principal(header_enc_tkt->client)) setflag(c_flags, KRB5_KDB_FLAG_CROSS_REALM); is_referral = krb5_is_tgs_principal(server.princ) && !krb5_principal_compare(kdc_context, tgs_server, server.princ); /* Check for protocol transition */ errcode = kdc_process_s4u2self_req(kdc_context, request, header_enc_tkt->client, &server, subkey, header_enc_tkt->session, kdc_time, &s4u_x509_user, &client, &c_nprincs, &status); if (errcode) goto cleanup; if (s4u_x509_user != NULL) setflag(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION); /* * We pick the session keytype here.... * * Some special care needs to be taken in the user-to-user * case, since we don't know what keytypes the application server * which is doing user-to-user authentication can support. We * know that it at least must be able to support the encryption * type of the session key in the TGT, since otherwise it won't be * able to decrypt the U2U ticket! So we use that in preference * to anything else. */ useenctype = 0; if (isflagset(request->kdc_options, KDC_OPT_ENC_TKT_IN_SKEY | KDC_OPT_CNAME_IN_ADDL_TKT)) { krb5_keyblock * st_sealing_key; krb5_kvno st_srv_kvno; krb5_enctype etype; krb5_db_entry st_client; int st_nprincs = 0; /* * Get the key for the second ticket, and decrypt it. */ if ((errcode = kdc_get_server_key(request->second_ticket[st_idx], c_flags, TRUE, /* match_enctype */ &st_client, &st_nprincs, &st_sealing_key, &st_srv_kvno))) { status = "2ND_TKT_SERVER"; goto cleanup; } errcode = krb5_decrypt_tkt_part(kdc_context, st_sealing_key, request->second_ticket[st_idx]); krb5_free_keyblock(kdc_context, st_sealing_key); if (errcode) { status = "2ND_TKT_DECRYPT"; krb5_db_free_principal(kdc_context, &st_client, st_nprincs); goto cleanup; } etype = request->second_ticket[st_idx]->enc_part2->session->enctype; if (!krb5_c_valid_enctype(etype)) { status = "BAD_ETYPE_IN_2ND_TKT"; errcode = KRB5KDC_ERR_ETYPE_NOSUPP; krb5_db_free_principal(kdc_context, &st_client, st_nprincs); goto cleanup; } for (i = 0; i < request->nktypes; i++) { if (request->ktype[i] == etype) { useenctype = etype; break; } } if (isflagset(request->kdc_options, KDC_OPT_CNAME_IN_ADDL_TKT)) { /* Do constrained delegation protocol and authorization checks */ errcode = kdc_process_s4u2proxy_req(kdc_context, request, request->second_ticket[st_idx]->enc_part2, &st_client, header_ticket->enc_part2->client, request->server, &status); if (errcode) goto cleanup; setflag(c_flags, KRB5_KDB_FLAG_CONSTRAINED_DELEGATION); assert(krb5_is_tgs_principal(header_ticket->server)); /* From now on, use evidence ticket as header ticket */ header_enc_tkt = request->second_ticket[st_idx]->enc_part2; assert(c_nprincs == 0); /* assured by kdc_process_s4u2self_req() */ client = st_client; c_nprincs = st_nprincs; } else { /* "client" is not used for user2user */ krb5_db_free_principal(kdc_context, &st_client, st_nprincs); } } /* * Select the keytype for the ticket session key. */ if ((useenctype == 0) && (useenctype = select_session_keytype(kdc_context, &server, request->nktypes, request->ktype)) == 0) { /* unsupported ktype */ status = "BAD_ENCRYPTION_TYPE"; errcode = KRB5KDC_ERR_ETYPE_NOSUPP; goto cleanup; } errcode = krb5_c_make_random_key(kdc_context, useenctype, &session_key); if (errcode) { /* random key failed */ status = "RANDOM_KEY_FAILED"; goto cleanup; } authtime = header_enc_tkt->times.authtime; if (is_referral) ticket_reply.server = server.princ; else ticket_reply.server = request->server; /* XXX careful for realm... */ enc_tkt_reply.flags = 0; enc_tkt_reply.times.starttime = 0; if (isflagset(server.attributes, KRB5_KDB_OK_AS_DELEGATE)) setflag(enc_tkt_reply.flags, TKT_FLG_OK_AS_DELEGATE); /* * Fix header_ticket's starttime; if it's zero, fill in the * authtime's value. */ if (!(header_enc_tkt->times.starttime)) header_enc_tkt->times.starttime = header_enc_tkt->times.authtime; /* don't use new addresses unless forwarded, see below */ enc_tkt_reply.caddrs = header_enc_tkt->caddrs; /* noaddrarray[0] = 0; */ reply_encpart.caddrs = 0;/* optional...don't put it in */ reply_encpart.enc_padata = NULL; /* It should be noted that local policy may affect the */ /* processing of any of these flags. For example, some */ /* realms may refuse to issue renewable tickets */ if (isflagset(request->kdc_options, KDC_OPT_FORWARDABLE)) { setflag(enc_tkt_reply.flags, TKT_FLG_FORWARDABLE); if (isflagset(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION)) { /* * If S4U2Self principal is not forwardable, then mark ticket as * unforwardable. This behaviour matches Windows, but it is * different to the MIT AS-REQ path, which returns an error * (KDC_ERR_POLICY) if forwardable tickets cannot be issued. * * Consider this block the S4U2Self equivalent to * validate_forwardable(). */ if (c_nprincs && isflagset(client.attributes, KRB5_KDB_DISALLOW_FORWARDABLE)) clear(enc_tkt_reply.flags, TKT_FLG_FORWARDABLE); /* * OK_TO_AUTH_AS_DELEGATE must be set on the service requesting * S4U2Self in order for forwardable tickets to be returned. */ else if (!is_referral && !isflagset(server.attributes, KRB5_KDB_OK_TO_AUTH_AS_DELEGATE)) clear(enc_tkt_reply.flags, TKT_FLG_FORWARDABLE); } } if (isflagset(request->kdc_options, KDC_OPT_FORWARDED)) { setflag(enc_tkt_reply.flags, TKT_FLG_FORWARDED); /* include new addresses in ticket & reply */ enc_tkt_reply.caddrs = request->addresses; reply_encpart.caddrs = request->addresses; } if (isflagset(header_enc_tkt->flags, TKT_FLG_FORWARDED)) setflag(enc_tkt_reply.flags, TKT_FLG_FORWARDED); if (isflagset(request->kdc_options, KDC_OPT_PROXIABLE)) setflag(enc_tkt_reply.flags, TKT_FLG_PROXIABLE); if (isflagset(request->kdc_options, KDC_OPT_PROXY)) { setflag(enc_tkt_reply.flags, TKT_FLG_PROXY); /* include new addresses in ticket & reply */ enc_tkt_reply.caddrs = request->addresses; reply_encpart.caddrs = request->addresses; } if (isflagset(request->kdc_options, KDC_OPT_ALLOW_POSTDATE)) setflag(enc_tkt_reply.flags, TKT_FLG_MAY_POSTDATE); if (isflagset(request->kdc_options, KDC_OPT_POSTDATED)) { setflag(enc_tkt_reply.flags, TKT_FLG_POSTDATED); setflag(enc_tkt_reply.flags, TKT_FLG_INVALID); enc_tkt_reply.times.starttime = request->from; } else enc_tkt_reply.times.starttime = kdc_time; if (isflagset(request->kdc_options, KDC_OPT_VALIDATE)) { assert(isflagset(c_flags, KRB5_KDB_FLAGS_S4U) == 0); /* BEWARE of allocation hanging off of ticket & enc_part2, it belongs to the caller */ ticket_reply = *(header_ticket); enc_tkt_reply = *(header_ticket->enc_part2); clear(enc_tkt_reply.flags, TKT_FLG_INVALID); } if (isflagset(request->kdc_options, KDC_OPT_RENEW)) { krb5_deltat old_life; assert(isflagset(c_flags, KRB5_KDB_FLAGS_S4U) == 0); /* BEWARE of allocation hanging off of ticket & enc_part2, it belongs to the caller */ ticket_reply = *(header_ticket); enc_tkt_reply = *(header_ticket->enc_part2); old_life = enc_tkt_reply.times.endtime - enc_tkt_reply.times.starttime; enc_tkt_reply.times.starttime = kdc_time; enc_tkt_reply.times.endtime = min(header_ticket->enc_part2->times.renew_till, kdc_time + old_life); } else { /* not a renew request */ enc_tkt_reply.times.starttime = kdc_time; until = (request->till == 0) ? kdc_infinity : request->till; enc_tkt_reply.times.endtime = min(until, min(enc_tkt_reply.times.starttime + server.max_life, min(enc_tkt_reply.times.starttime + max_life_for_realm, header_enc_tkt->times.endtime))); if (isflagset(request->kdc_options, KDC_OPT_RENEWABLE_OK) && (enc_tkt_reply.times.endtime < request->till) && isflagset(header_enc_tkt->flags, TKT_FLG_RENEWABLE)) { setflag(request->kdc_options, KDC_OPT_RENEWABLE); request->rtime = min(request->till, header_enc_tkt->times.renew_till); } } rtime = (request->rtime == 0) ? kdc_infinity : request->rtime; if (isflagset(request->kdc_options, KDC_OPT_RENEWABLE)) { /* already checked above in policy check to reject request for a renewable ticket using a non-renewable ticket */ setflag(enc_tkt_reply.flags, TKT_FLG_RENEWABLE); enc_tkt_reply.times.renew_till = min(rtime, min(header_enc_tkt->times.renew_till, enc_tkt_reply.times.starttime + min(server.max_renewable_life, max_renewable_life_for_realm))); } else { enc_tkt_reply.times.renew_till = 0; } /* * Set authtime to be the same as header_ticket's */ enc_tkt_reply.times.authtime = header_enc_tkt->times.authtime; /* * Propagate the preauthentication flags through to the returned ticket. */ if (isflagset(header_enc_tkt->flags, TKT_FLG_PRE_AUTH)) setflag(enc_tkt_reply.flags, TKT_FLG_PRE_AUTH); if (isflagset(header_enc_tkt->flags, TKT_FLG_HW_AUTH)) setflag(enc_tkt_reply.flags, TKT_FLG_HW_AUTH); /* starttime is optional, and treated as authtime if not present. so we can nuke it if it matches */ if (enc_tkt_reply.times.starttime == enc_tkt_reply.times.authtime) enc_tkt_reply.times.starttime = 0; if (isflagset(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION)) { errcode = krb5_unparse_name(kdc_context, s4u_x509_user->user_id.user, &s4u_name); } else if (isflagset(c_flags, KRB5_KDB_FLAG_CONSTRAINED_DELEGATION)) { errcode = krb5_unparse_name(kdc_context, header_enc_tkt->client, &s4u_name); } else { errcode = 0; } if (errcode) { status = "UNPARSING S4U CLIENT"; goto cleanup; } if (isflagset(request->kdc_options, KDC_OPT_ENC_TKT_IN_SKEY)) { krb5_enc_tkt_part *t2enc = request->second_ticket[st_idx]->enc_part2; encrypting_key = *(t2enc->session); } else { /* * Find the server key */ if ((errcode = krb5_dbe_find_enctype(kdc_context, &server, -1, /* ignore keytype */ -1, /* Ignore salttype */ 0,/* Get highest kvno */ &server_key))) { status = "FINDING_SERVER_KEY"; goto cleanup; } if ((errcode = krb5_dbe_find_mkey(kdc_context, master_keylist, &server, &mkey_ptr))) { krb5_keylist_node *tmp_mkey_list; /* try refreshing master key list */ /* XXX it would nice if we had the mkvno here for optimization */ if (krb5_db_fetch_mkey_list(kdc_context, master_princ, &master_keyblock, 0, &tmp_mkey_list) == 0) { krb5_dbe_free_key_list(kdc_context, master_keylist); master_keylist = tmp_mkey_list; if ((errcode = krb5_dbe_find_mkey(kdc_context, master_keylist, &server, &mkey_ptr))) { status = "FINDING_MASTER_KEY"; goto cleanup; } } else { status = "FINDING_MASTER_KEY"; goto cleanup; } } /* convert server.key into a real key (it may be encrypted * in the database) */ if ((errcode = krb5_dbekd_decrypt_key_data(kdc_context, mkey_ptr, server_key, &encrypting_key, NULL))) { status = "DECRYPT_SERVER_KEY"; goto cleanup; } } if (isflagset(c_flags, KRB5_KDB_FLAG_CONSTRAINED_DELEGATION)) { /* * Don't allow authorization data to be disabled if constrained * delegation is requested. We don't want to deny the server * the ability to validate that delegation was used. */ clear(server.attributes, KRB5_KDB_NO_AUTH_DATA_REQUIRED); } if (isflagset(server.attributes, KRB5_KDB_NO_AUTH_DATA_REQUIRED) == 0) { /* * If we are not doing protocol transition/constrained delegation * and there was no authorization data included, try to lookup * the client principal as it may be mapped to a local account. * * Always validate authorization data for constrained delegation * because we must validate the KDC signatures. */ if (!isflagset(c_flags, KRB5_KDB_FLAGS_S4U) && header_enc_tkt->authorization_data == NULL) { /* Generate authorization data so we can include it in ticket */ setflag(c_flags, KRB5_KDB_FLAG_INCLUDE_PAC); /* Map principals from foreign (possibly non-AD) realms */ setflag(c_flags, KRB5_KDB_FLAG_MAP_PRINCIPALS); assert(c_nprincs == 0); /* should not have been looked up already */ c_nprincs = 1; errcode = krb5_db_get_principal_ext(kdc_context, header_enc_tkt->client, c_flags, &client, &c_nprincs, &more); /* * We can ignore errors because the principal may be a * valid cross-realm principal for which we have no local * mapping. But we do want to check that at most one entry * was returned. */ if (errcode == 0 && (more || c_nprincs > 1)) { errcode = KRB5KDC_ERR_PRINCIPAL_NOT_UNIQUE; goto cleanup; } else if (errcode) { c_nprincs = 0; } } } enc_tkt_reply.authorization_data = NULL; if (isflagset(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION) && !isflagset(c_flags, KRB5_KDB_FLAG_CROSS_REALM)) enc_tkt_reply.client = s4u_x509_user->user_id.user; else enc_tkt_reply.client = header_enc_tkt->client; enc_tkt_reply.session = &session_key; enc_tkt_reply.transited.tr_type = KRB5_DOMAIN_X500_COMPRESS; enc_tkt_reply.transited.tr_contents = empty_string; /* equivalent of "" */ errcode = handle_authdata(kdc_context, c_flags, (c_nprincs != 0) ? &client : NULL, &server, (k_nprincs != 0) ? &krbtgt : NULL, subkey != NULL ? subkey : header_ticket->enc_part2->session, &encrypting_key, /* U2U or server key */ pkt, request, s4u_x509_user ? s4u_x509_user->user_id.user : NULL, header_enc_tkt, &enc_tkt_reply); if (errcode) { krb5_klog_syslog(LOG_INFO, "TGS_REQ : handle_authdata (%d)", errcode); status = "HANDLE_AUTHDATA"; goto cleanup; } if (is_referral && isflagset(s_flags, KRB5_KDB_FLAG_CANONICALIZE)) { errcode = return_svr_referral_data(kdc_context, &server, &reply_encpart); if (errcode) { status = "KDC_RETURN_ENC_PADATA"; goto cleanup; } } /* * Only add the realm of the presented tgt to the transited list if * it is different than the local realm (cross-realm) and it is different * than the realm of the client (since the realm of the client is already * implicitly part of the transited list and should not be explicitly * listed). */ /* realm compare is like strcmp, but knows how to deal with these args */ if (realm_compare(header_ticket->server, tgs_server) || realm_compare(header_ticket->server, enc_tkt_reply.client)) { /* tgt issued by local realm or issued by realm of client */ enc_tkt_reply.transited = header_enc_tkt->transited; } else { /* tgt issued by some other realm and not the realm of the client */ /* assemble new transited field into allocated storage */ if (header_enc_tkt->transited.tr_type != KRB5_DOMAIN_X500_COMPRESS) { status = "BAD_TRTYPE"; errcode = KRB5KDC_ERR_TRTYPE_NOSUPP; goto cleanup; } enc_tkt_transited.tr_type = KRB5_DOMAIN_X500_COMPRESS; enc_tkt_transited.magic = 0; enc_tkt_transited.tr_contents.magic = 0; enc_tkt_transited.tr_contents.data = 0; enc_tkt_transited.tr_contents.length = 0; enc_tkt_reply.transited = enc_tkt_transited; if ((errcode = add_to_transited(&header_enc_tkt->transited.tr_contents, &enc_tkt_reply.transited.tr_contents, header_ticket->server, enc_tkt_reply.client, request->server))) { status = "ADD_TR_FAIL"; goto cleanup; } newtransited = 1; } if (isflagset(c_flags, KRB5_KDB_FLAG_CROSS_REALM)) { errcode = validate_transit_path(kdc_context, header_enc_tkt->client, &server, (k_nprincs != 0) ? &krbtgt : NULL); if (errcode) { status = "NON_TRANSITIVE"; goto cleanup; } } if (!isflagset (request->kdc_options, KDC_OPT_DISABLE_TRANSITED_CHECK)) { unsigned int tlen; char *tdots; errcode = kdc_check_transited_list (kdc_context, &enc_tkt_reply.transited.tr_contents, krb5_princ_realm (kdc_context, header_enc_tkt->client), krb5_princ_realm (kdc_context, request->server)); tlen = enc_tkt_reply.transited.tr_contents.length; tdots = tlen > 125 ? "..." : ""; tlen = tlen > 125 ? 125 : tlen; if (errcode == 0) { setflag (enc_tkt_reply.flags, TKT_FLG_TRANSIT_POLICY_CHECKED); } else if (errcode == KRB5KRB_AP_ERR_ILL_CR_TKT) krb5_klog_syslog (LOG_INFO, "bad realm transit path from '%s' to '%s' " "via '%.*s%s'", cname ? cname : "<unknown client>", sname ? sname : "<unknown server>", tlen, enc_tkt_reply.transited.tr_contents.data, tdots); else { emsg = krb5_get_error_message(kdc_context, errcode); krb5_klog_syslog (LOG_ERR, "unexpected error checking transit from " "'%s' to '%s' via '%.*s%s': %s", cname ? cname : "<unknown client>", sname ? sname : "<unknown server>", tlen, enc_tkt_reply.transited.tr_contents.data, tdots, emsg); krb5_free_error_message(kdc_context, emsg); emsg = NULL; } } else krb5_klog_syslog (LOG_INFO, "not checking transit path"); if (reject_bad_transit && !isflagset (enc_tkt_reply.flags, TKT_FLG_TRANSIT_POLICY_CHECKED)) { errcode = KRB5KDC_ERR_POLICY; status = "BAD_TRANSIT"; goto cleanup; } ticket_reply.enc_part2 = &enc_tkt_reply; /* * If we are doing user-to-user authentication, then make sure * that the client for the second ticket matches the request * server, and then encrypt the ticket using the session key of * the second ticket. */ if (isflagset(request->kdc_options, KDC_OPT_ENC_TKT_IN_SKEY)) { /* * Make sure the client for the second ticket matches * requested server. */ krb5_enc_tkt_part *t2enc = request->second_ticket[st_idx]->enc_part2; krb5_principal client2 = t2enc->client; if (!krb5_principal_compare(kdc_context, request->server, client2)) { if ((errcode = krb5_unparse_name(kdc_context, client2, &altcname))) altcname = 0; if (altcname != NULL) limit_string(altcname); errcode = KRB5KDC_ERR_SERVER_NOMATCH; status = "2ND_TKT_MISMATCH"; goto cleanup; } ticket_kvno = 0; ticket_reply.enc_part.enctype = t2enc->session->enctype; st_idx++; } else { ticket_kvno = server_key->key_data_kvno; } errcode = krb5_encrypt_tkt_part(kdc_context, &encrypting_key, &ticket_reply); if (!isflagset(request->kdc_options, KDC_OPT_ENC_TKT_IN_SKEY)) krb5_free_keyblock_contents(kdc_context, &encrypting_key); if (errcode) { status = "TKT_ENCRYPT"; goto cleanup; } ticket_reply.enc_part.kvno = ticket_kvno; /* Start assembling the response */ reply.msg_type = KRB5_TGS_REP; reply.padata = 0;/* always */ if (isflagset(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION) && find_pa_data(request->padata, KRB5_PADATA_S4U_X509_USER) != NULL) { errcode = kdc_make_s4u2self_rep(kdc_context, subkey, header_ticket->enc_part2->session, s4u_x509_user, &reply, &reply_encpart); if (errcode) { status = "KDC_RETURN_S4U2SELF_PADATA"; goto cleanup; } } reply.client = enc_tkt_reply.client; reply.enc_part.kvno = 0;/* We are using the session key */ reply.ticket = &ticket_reply; reply_encpart.session = &session_key; reply_encpart.nonce = request->nonce; /* copy the time fields EXCEPT for authtime; its location is used for ktime */ reply_encpart.times = enc_tkt_reply.times; reply_encpart.times.authtime = header_enc_tkt->times.authtime; /* starttime is optional, and treated as authtime if not present. so we can nuke it if it matches */ if (enc_tkt_reply.times.starttime == enc_tkt_reply.times.authtime) enc_tkt_reply.times.starttime = 0; nolrentry.lr_type = KRB5_LRQ_NONE; nolrentry.value = 0; nolrarray[0] = &nolrentry; nolrarray[1] = 0; reply_encpart.last_req = nolrarray; /* not available for TGS reqs */ reply_encpart.key_exp = 0;/* ditto */ reply_encpart.flags = enc_tkt_reply.flags; reply_encpart.server = ticket_reply.server; /* use the session key in the ticket, unless there's a subsession key in the AP_REQ */ reply.enc_part.enctype = subkey ? subkey->enctype : header_ticket->enc_part2->session->enctype; errcode = kdc_fast_response_handle_padata(state, request, &reply, subkey?subkey->enctype:header_ticket->enc_part2->session->enctype); if (errcode !=0 ) { status = "Preparing FAST padata"; goto cleanup; } errcode =kdc_fast_handle_reply_key(state, subkey?subkey:header_ticket->enc_part2->session, &reply_key); if (errcode) { status = "generating reply key"; goto cleanup; } errcode = krb5_encode_kdc_rep(kdc_context, KRB5_TGS_REP, &reply_encpart, subkey ? 1 : 0, reply_key, &reply, response); if (errcode) { status = "ENCODE_KDC_REP"; } else { status = "ISSUE"; } memset(ticket_reply.enc_part.ciphertext.data, 0, ticket_reply.enc_part.ciphertext.length); free(ticket_reply.enc_part.ciphertext.data); /* these parts are left on as a courtesy from krb5_encode_kdc_rep so we can use them in raw form if needed. But, we don't... */ memset(reply.enc_part.ciphertext.data, 0, reply.enc_part.ciphertext.length); free(reply.enc_part.ciphertext.data); cleanup: assert(status != NULL); if (reply_key) krb5_free_keyblock(kdc_context, reply_key); if (errcode) emsg = krb5_get_error_message (kdc_context, errcode); log_tgs_req(from, request, &reply, cname, sname, altcname, authtime, c_flags, s4u_name, status, errcode, emsg); if (errcode) { krb5_free_error_message (kdc_context, emsg); emsg = NULL; } if (errcode) { int got_err = 0; if (status == 0) { status = krb5_get_error_message (kdc_context, errcode); got_err = 1; } errcode -= ERROR_TABLE_BASE_krb5; if (errcode < 0 || errcode > 128) errcode = KRB_ERR_GENERIC; retval = prepare_error_tgs(state, request, header_ticket, errcode, nprincs ? server.princ : NULL, response, status); if (got_err) { krb5_free_error_message (kdc_context, status); status = 0; } } if (header_ticket != NULL) krb5_free_ticket(kdc_context, header_ticket); if (request != NULL) krb5_free_kdc_req(kdc_context, request); if (state) kdc_free_rstate(state); if (cname != NULL) free(cname); if (sname != NULL) free(sname); if (nprincs != 0) krb5_db_free_principal(kdc_context, &server, 1); if (session_key.contents != NULL) krb5_free_keyblock_contents(kdc_context, &session_key); if (newtransited) free(enc_tkt_reply.transited.tr_contents.data); if (k_nprincs) krb5_db_free_principal(kdc_context, &krbtgt, k_nprincs); if (c_nprincs) krb5_db_free_principal(kdc_context, &client, c_nprincs); if (s4u_x509_user != NULL) krb5_free_pa_s4u_x509_user(kdc_context, s4u_x509_user); if (kdc_issued_auth_data != NULL) krb5_free_authdata(kdc_context, kdc_issued_auth_data); if (s4u_name != NULL) free(s4u_name); if (subkey != NULL) krb5_free_keyblock(kdc_context, subkey); if (reply.padata) krb5_free_pa_data(kdc_context, reply.padata); if (reply_encpart.enc_padata) krb5_free_pa_data(kdc_context, reply_encpart.enc_padata); return retval; }
/* * May the fleas of a thousand camels infest the ISO, they who think * that arbitrarily large multi-component names are a Good Thing..... */ static krb5_error_code k5_parse_name(krb5_context context, const char *name, int flags, krb5_principal *nprincipal) { register const char *cp; register char *q; register int i,c,size; int components = 0; const char *parsed_realm = NULL; int fcompsize[FCOMPNUM]; unsigned int realmsize = 0; char *default_realm = NULL; int default_realm_size = 0; char *tmpdata; krb5_principal principal; krb5_error_code retval; unsigned int enterprise = (flags & KRB5_PRINCIPAL_PARSE_ENTERPRISE); int first_at; /* * Pass 1. Find out how many components there are to the name, * and get string sizes for the first FCOMPNUM components. For * enterprise principal names (UPNs), there is only a single * component. */ size = 0; for (i=0,cp = name, first_at = 1; (c = *cp); cp++) { if (c == QUOTECHAR) { cp++; if (!(c = *cp)) /* * QUOTECHAR can't be at the last * character of the name! */ return(KRB5_PARSE_MALFORMED); size++; continue; } else if (c == COMPONENT_SEP && !enterprise) { if (parsed_realm) /* * Shouldn't see a component separator * after we've parsed out the realm name! */ return(KRB5_PARSE_MALFORMED); if (i < FCOMPNUM) { fcompsize[i] = size; } size = 0; i++; } else if (c == REALM_SEP && (!enterprise || !first_at)) { if (parsed_realm) /* * Multiple realm separaters * not allowed; zero-length realms are. */ return(KRB5_PARSE_MALFORMED); parsed_realm = cp + 1; if (i < FCOMPNUM) { fcompsize[i] = size; } size = 0; } else { if (c == REALM_SEP && enterprise && first_at) first_at = 0; size++; } } if (parsed_realm != NULL) realmsize = size; else if (i < FCOMPNUM) fcompsize[i] = size; components = i + 1; /* * Now, we allocate the principal structure and all of its * component pieces */ principal = (krb5_principal)malloc(sizeof(krb5_principal_data)); if (principal == NULL) { return(ENOMEM); } principal->data = (krb5_data *) malloc(sizeof(krb5_data) * components); if (principal->data == NULL) { krb5_xfree((char *)principal); return ENOMEM; } principal->length = components; /* * If a realm was not found, then use the default realm, unless * KRB5_PRINCIPAL_PARSE_NO_REALM was specified in which case the * realm will be empty. */ if (!parsed_realm) { if (flags & KRB5_PRINCIPAL_PARSE_REQUIRE_REALM) { krb5_set_error_message(context, KRB5_PARSE_MALFORMED, "Principal %s is missing required realm", name); krb5_xfree(principal->data); krb5_xfree(principal); return KRB5_PARSE_MALFORMED; } if (!default_realm && (flags & KRB5_PRINCIPAL_PARSE_NO_REALM) == 0) { retval = krb5_get_default_realm(context, &default_realm); if (retval) { krb5_xfree(principal->data); krb5_xfree((char *)principal); return(retval); } default_realm_size = strlen(default_realm); } realmsize = default_realm_size; } else if (flags & KRB5_PRINCIPAL_PARSE_NO_REALM) { krb5_set_error_message(context, KRB5_PARSE_MALFORMED, "Principal %s has realm present", name); krb5_xfree(principal->data); krb5_xfree(principal); return KRB5_PARSE_MALFORMED; } /* * Pass 2. Happens only if there were more than FCOMPNUM * component; if this happens, someone should be shot * immediately. Nevertheless, we will attempt to handle said * case..... <martyred sigh> */ if (components >= FCOMPNUM) { size = 0; parsed_realm = NULL; for (i=0,cp = name; (c = *cp); cp++) { if (c == QUOTECHAR) { cp++; size++; } else if (c == COMPONENT_SEP) { if (krb5_princ_size(context, principal) > i) krb5_princ_component(context, principal, i)->length = size; size = 0; i++; } else if (c == REALM_SEP) { if (krb5_princ_size(context, principal) > i) krb5_princ_component(context, principal, i)->length = size; size = 0; parsed_realm = cp+1; } else size++; } if (parsed_realm) krb5_princ_realm(context, principal)->length = size; else if (krb5_princ_size(context, principal) > i) krb5_princ_component(context, principal, i)->length = size; if (i + 1 != components) { #if !defined(_WIN32) fprintf(stderr, "Programming error in krb5_parse_name!"); #endif assert(i + 1 == components); abort(); } } else { /* * If there were fewer than FCOMPSIZE components (the * usual case), then just copy the sizes to the * principal structure */ for (i=0; i < components; i++) krb5_princ_component(context, principal, i)->length = fcompsize[i]; } /* * Now, we need to allocate the space for the strings themselves..... */ tmpdata = malloc(realmsize + 1); if (tmpdata == 0) { krb5_xfree(principal->data); krb5_xfree(principal); krb5_xfree(default_realm); return ENOMEM; } krb5_princ_set_realm_length(context, principal, realmsize); krb5_princ_set_realm_data(context, principal, tmpdata); for (i=0; i < components; i++) { char *tmpdata2 = malloc(krb5_princ_component(context, principal, i)->length + 1); if (tmpdata2 == NULL) { for (i--; i >= 0; i--) krb5_xfree(krb5_princ_component(context, principal, i)->data); krb5_xfree(krb5_princ_realm(context, principal)->data); krb5_xfree(principal->data); krb5_xfree(principal); krb5_xfree(default_realm); return(ENOMEM); } krb5_princ_component(context, principal, i)->data = tmpdata2; krb5_princ_component(context, principal, i)->magic = KV5M_DATA; } /* * Pass 3. Now we go through the string a *third* time, this * time filling in the krb5_principal structure which we just * allocated. */ q = krb5_princ_component(context, principal, 0)->data; for (i=0,cp = name, first_at = 1; (c = *cp); cp++) { if (c == QUOTECHAR) { cp++; switch (c = *cp) { case 'n': *q++ = '\n'; break; case 't': *q++ = '\t'; break; case 'b': *q++ = '\b'; break; case '0': *q++ = '\0'; break; default: *q++ = c; break; } } else if (c == COMPONENT_SEP && !enterprise) { i++; *q++ = '\0'; q = krb5_princ_component(context, principal, i)->data; } else if (c == REALM_SEP && (!enterprise || !first_at)) { i++; *q++ = '\0'; q = krb5_princ_realm(context, principal)->data; } else { if (c == REALM_SEP && enterprise && first_at) first_at = 0; *q++ = c; } } *q++ = '\0'; if (!parsed_realm) { if (flags & KRB5_PRINCIPAL_PARSE_NO_REALM) (krb5_princ_realm(context, principal)->data)[0] = '\0'; else strlcpy(krb5_princ_realm(context, principal)->data, default_realm, realmsize+1); } /* * Alright, we're done. Now stuff a pointer to this monstrosity * into the return variable, and let's get out of here. */ if (enterprise) krb5_princ_type(context, principal) = KRB5_NT_ENTERPRISE_PRINCIPAL; else krb5_princ_type(context, principal) = KRB5_NT_PRINCIPAL; principal->magic = KV5M_PRINCIPAL; principal->realm.magic = KV5M_DATA; *nprincipal = principal; if (default_realm != NULL) krb5_xfree(default_realm); return(0); }