# # Copyright 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. PROG= krb5kdc MANIFEST= krb5kdc.xml OBJS = \ dispatch.o\ do_as_req.o\ do_tgs_req.o\ extern.o\ kdc_preauth.o\ kdc_util.o\ main.o\ network.o\ policy.o\ replay.o \ sock2p.o SRCS = $(OBJS:.o=.c) DEFS = -DHAVE_LIBSOCKET=1 -DHAVE_LIBNSL=1 \ -DHAVE_SYSLOG_H=1 -DHAVE_STDARG_H=1 \ -DHAVE_SYS_SELECT_H=1 -DHAVE_OPENLOG=1 \ -DHAVE_SYSLOG=1 -DHAVE_CLOSELOG=1 -DHAVE_STRFTIME=1\ -DHAVE_VSPRINTF=1 -DHAVE_COMPILE=1 -DHAVE_STEP=1 \ -DHAVE_NETINET_IN_H=1 -DHAVE_INET_NTOP=1 \ -DHAVE_SYS_SOCKIO_H=1 -DHAVE_SYS_SELECT_H=1 CLOBBERFILES += $(RSRC) include ../../Makefile.cmd include $(SRC)/lib/gss_mechs/mech_krb5/Makefile.mech_krb5 POFILE = $(PROG).po POFILES = generic.po ROOTMANIFESTDIR= $(ROOTSVCNETWORKSECURITY) CFLAGS += $(CCOPTS) $(DEFS) $(LOCALINCLUDE) # The SET macros in network.c will be mis-optimized if GCC believes it # may rely on undefined signed overflow behaviour. CFLAGS += -fno-strict-overflow CPPFLAGS += \ -I$(SRC)/lib/gss_mechs/mech_krb5/include \ -I$(SRC)/lib/gss_mechs/mech_krb5/include/krb5\ -I$(SRC)/uts/common/gssapi/mechs/krb5/include \ -DHAVE_SYSLOG_H=1 CERRWARN += -Wno-unused-variable CERRWARN += -Wno-unused-function CERRWARN += -Wno-type-limits CERRWARN += -Wno-implicit-function-declaration CERRWARN += -Wno-parentheses # not linted SMATCH=off LDFLAGS += $(KRUNPATH) $(KERBRUNPATH) # Hammerhead: GNU ld needs rpath-link for transitive deps (libdyn, libkdb) via libkadm5srv LDFLAGS += -Wl,-rpath-link,$(ROOT)/usr/lib/krb5 LDLIBS += -L$(ROOT_KLIBDIR) -L$(KRB5LIB) \ -lmech_krb5 -lkdb -lkadm5srv -lnsl -lsocket -lbsm -lc .KEEP_STATE: all: $(PROG) $(PROG): $(OBJS) $(LINK.c) $(OBJS) -o $@ $(LDLIBS) $(POST_PROCESS) $(KRB5LIBPROG): FILEMODE = 500 install: $(KRB5LIBPROG) $(ROOTSVCMETHOD) $(ROOTMANIFEST) check: $(CHKMANIFEST) clean: $(RM) $(OBJS) include ../../Makefile.targ $(POFILE): $(DERIVED_FILES) .WAIT $(POFILES) $(RM) $@ $(CAT) $(POFILES) > $@ generic.po: FRC $(RM) messages.po $(XGETTEXT) $(XGETFLAGS) `$(GREP) -l gettext *.[ch]` $(SED) "/^domain/d" messages.po > $@ $(RM) messages.po FRC: /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * kdc/dispatch.c * * Copyright 1990 by the Massachusetts Institute of Technology. * * Export of this software from the United States of America may * require a specific license from the United States Government. * It is the responsibility of any person or organization contemplating * export to obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of M.I.T. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. Furthermore if you modify this software you must label * your software as modified software and not distribute it in such a * fashion that it might be confused with the original M.I.T. software. * M.I.T. makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. * * * Dispatch an incoming packet. */ #include "k5-int.h" #include #include "kdc_util.h" #include "extern.h" #include "adm_proto.h" #include #include #include static krb5_int32 last_usec = 0, last_os_random = 0; krb5_error_code dispatch(krb5_data *pkt, const krb5_fulladdr *from, krb5_data **response) { krb5_error_code retval; krb5_kdc_req *as_req; krb5_int32 now, now_usec; /* decode incoming packet, and dispatch */ #ifndef NOCACHE /* try the replay lookaside buffer */ if (kdc_check_lookaside(pkt, response)) { /* a hit! */ const char *name = 0; char buf[46]; name = (char *) inet_ntop (ADDRTYPE2FAMILY (from->address->addrtype), from->address->contents, buf, sizeof (buf)); if (name == 0) name = "[unknown address type]"; krb5_klog_syslog(LOG_INFO, "DISPATCH: repeated (retransmitted?) request from %s, resending previous response", name); return 0; } #endif /* SUNW14resync XXX */ #if 0 retval = krb5_crypto_us_timeofday(&now, &now_usec); if (retval == 0) { krb5_int32 usec_difference = now_usec-last_usec; krb5_data data; if(last_os_random == 0) last_os_random = now; /* Grab random data from OS every hour*/ if(now-last_os_random >= 60*60) { krb5_c_random_os_entropy(kdc_context, 0, NULL); last_os_random = now; } data.length = sizeof(krb5_int32); data.data = (void *) &usec_difference; krb5_c_random_add_entropy(kdc_context, KRB5_C_RANDSOURCE_TIMING, &data); last_usec = now_usec; } #endif /* try TGS_REQ first; they are more common! */ if (krb5_is_tgs_req(pkt)) { retval = process_tgs_req(pkt, from, response); } else if (krb5_is_as_req(pkt)) { if (!(retval = decode_krb5_as_req(pkt, &as_req))) { /* * setup_server_realm() sets up the global realm-specific data * pointer. */ if (!(retval = setup_server_realm(as_req->server))) { retval = process_as_req(as_req, pkt, from, response); } krb5_free_kdc_req(kdc_context, as_req); } } #ifdef KRB5_KRB4_COMPAT else if (pkt->data[0] == 4) /* old version */ retval = process_v4(pkt, from, response); #endif else retval = KRB5KRB_AP_ERR_MSG_TYPE; #ifndef NOCACHE /* put the response into the lookaside buffer */ if (!retval) kdc_insert_lookaside(pkt, *response); #endif return retval; } /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * kdc/do_as_req.c * * Copyright 1990,1991 by the Massachusetts Institute of Technology. * All Rights Reserved. * * Export of this software from the United States of America may * require a specific license from the United States Government. * It is the responsibility of any person or organization contemplating * export to obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of M.I.T. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. Furthermore if you modify this software you must label * your software as modified software and not distribute it in such a * fashion that it might be confused with the original M.I.T. software. * M.I.T. makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. * * * KDC Routines to deal with AS_REQ's */ #define NEED_SOCKETS #include "k5-int.h" #include "com_err.h" #include #ifdef HAVE_NETINET_IN_H #include #include #ifndef hpux #include #endif /* hpux */ #endif /* HAVE_NETINET_IN_H */ #include "kdc_util.h" #include "policy.h" #include "adm.h" #include "adm_proto.h" #include "extern.h" static krb5_error_code prepare_error_as (krb5_kdc_req *, int, krb5_data *, krb5_data **, const char *); /*ARGSUSED*/ krb5_error_code process_as_req(krb5_kdc_req *request, krb5_data *req_pkt, const krb5_fulladdr *from, krb5_data **response) { krb5_db_entry client, server; krb5_kdc_rep reply; krb5_enc_kdc_rep_part reply_encpart; krb5_ticket ticket_reply; krb5_enc_tkt_part enc_tkt_reply; krb5_error_code errcode; int c_nprincs = 0, s_nprincs = 0; krb5_boolean more; krb5_timestamp kdc_time, authtime, etime = 0; krb5_keyblock session_key; krb5_keyblock encrypting_key; const char *status; krb5_key_data *server_key, *client_key; krb5_enctype useenctype; #ifdef KRBCONF_KDC_MODIFIES_KDB krb5_boolean update_client = 0; #endif /* KRBCONF_KDC_MODIFIES_KDB */ krb5_data e_data; register int i; krb5_timestamp until, rtime; long long tmp_client_times, tmp_server_times, tmp_realm_times; char *cname = 0, *sname = 0; const char *fromstring = 0; char ktypestr[128]; char rep_etypestr[128]; char fromstringbuf[70]; void *pa_context = NULL; struct in_addr from_in4; /* IPv4 address of sender */ ticket_reply.enc_part.ciphertext.data = 0; e_data.data = 0; (void) memset(&encrypting_key, 0, sizeof(krb5_keyblock)); reply.padata = 0; /* avoid bogus free in error_out */ (void) memset(&session_key, 0, sizeof(krb5_keyblock)); enc_tkt_reply.authorization_data = NULL; ktypes2str(ktypestr, sizeof(ktypestr), request->nktypes, request->ktype); (void) memcpy(&from_in4, from->address->contents, /* SUNW */ sizeof (struct in_addr)); fromstring = inet_ntop(ADDRTYPE2FAMILY (from->address->addrtype), &from_in4, fromstringbuf, sizeof(fromstringbuf)); if (!fromstring) fromstring = ""; if (!request->client) { status = "NULL_CLIENT"; errcode = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; goto errout; } if ((errcode = krb5_unparse_name(kdc_context, request->client, &cname))) { status = "UNPARSING_CLIENT"; goto errout; } limit_string(cname); if (!request->server) { status = "NULL_SERVER"; errcode = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; goto errout; } if ((errcode = krb5_unparse_name(kdc_context, request->server, &sname))) { status = "UNPARSING_SERVER"; goto errout; } limit_string(sname); c_nprincs = 1; if ((errcode = krb5_db_get_principal(kdc_context, request->client, &client, &c_nprincs, &more))) { status = "LOOKING_UP_CLIENT"; c_nprincs = 0; goto errout; } if (more) { status = "NON-UNIQUE_CLIENT"; errcode = KRB5KDC_ERR_PRINCIPAL_NOT_UNIQUE; goto errout; } else if (c_nprincs != 1) { status = "CLIENT_NOT_FOUND"; #ifdef KRBCONF_VAGUE_ERRORS errcode = KRB5KRB_ERR_GENERIC; #else errcode = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; #endif goto errout; } s_nprincs = 1; if ((errcode = krb5_db_get_principal(kdc_context, request->server, &server, &s_nprincs, &more))) { status = "LOOKING_UP_SERVER"; goto errout; } if (more) { status = "NON-UNIQUE_SERVER"; errcode = KRB5KDC_ERR_PRINCIPAL_NOT_UNIQUE; goto errout; } else if (s_nprincs != 1) { status = "SERVER_NOT_FOUND"; errcode = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; goto errout; } if ((errcode = krb5_timeofday(kdc_context, &kdc_time))) { status = "TIMEOFDAY"; goto errout; } if ((errcode = validate_as_request(request, client, server, kdc_time, &status))) { if (!status) status = "UNKNOWN_REASON"; errcode += ERROR_TABLE_BASE_krb5; goto errout; } /* * Select the keytype for the ticket session key. */ if ((useenctype = select_session_keytype(kdc_context, &server, request->nktypes, request->ktype)) == 0) { /* unsupported ktype */ status = "BAD_ENCRYPTION_TYPE"; errcode = KRB5KDC_ERR_ETYPE_NOSUPP; goto errout; } if ((errcode = krb5_c_make_random_key(kdc_context, useenctype, &session_key))) { /* random key failed */ status = "RANDOM_KEY_FAILED"; goto errout; } ticket_reply.server = request->server; enc_tkt_reply.flags = 0; setflag(enc_tkt_reply.flags, TKT_FLG_INITIAL); /* 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_PROXIABLE)) setflag(enc_tkt_reply.flags, TKT_FLG_PROXIABLE); if (isflagset(request->kdc_options, KDC_OPT_ALLOW_POSTDATE)) setflag(enc_tkt_reply.flags, TKT_FLG_MAY_POSTDATE); enc_tkt_reply.session = &session_key; enc_tkt_reply.client = request->client; enc_tkt_reply.transited.tr_type = KRB5_DOMAIN_X500_COMPRESS; enc_tkt_reply.transited.tr_contents = empty_string; /* equivalent of "" */ enc_tkt_reply.times.authtime = kdc_time; 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; until = (request->till == 0) ? kdc_infinity : request->till; /* These numbers could easily be large * use long long variables to ensure that they don't * result in negative values when added. */ tmp_client_times = (long long) enc_tkt_reply.times.starttime + client.max_life; tmp_server_times = (long long) enc_tkt_reply.times.starttime + server.max_life; tmp_realm_times = (long long) enc_tkt_reply.times.starttime + max_life_for_realm; enc_tkt_reply.times.endtime = min(until, min(tmp_client_times, min(tmp_server_times, min(tmp_realm_times,KRB5_KDB_EXPIRATION)))); if (isflagset(request->kdc_options, KDC_OPT_RENEWABLE_OK) && !isflagset(client.attributes, KRB5_KDB_DISALLOW_RENEWABLE) && (enc_tkt_reply.times.endtime < request->till)) { /* we set the RENEWABLE option for later processing */ setflag(request->kdc_options, KDC_OPT_RENEWABLE); request->rtime = request->till; } rtime = (request->rtime == 0) ? kdc_infinity : request->rtime; if (isflagset(request->kdc_options, KDC_OPT_RENEWABLE)) { /* * XXX Should we squelch the output renew_till to be no * earlier than the endtime of the ticket? */ setflag(enc_tkt_reply.flags, TKT_FLG_RENEWABLE); tmp_client_times = (double) enc_tkt_reply.times.starttime + client.max_renewable_life; tmp_server_times = (double) enc_tkt_reply.times.starttime + server.max_renewable_life; tmp_realm_times = (double) enc_tkt_reply.times.starttime + max_renewable_life_for_realm; enc_tkt_reply.times.renew_till = min(rtime, min(tmp_client_times, min(tmp_server_times, min(tmp_realm_times,KRB5_KDB_EXPIRATION)))); } else enc_tkt_reply.times.renew_till = 0; /* XXX */ /* 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; enc_tkt_reply.caddrs = request->addresses; enc_tkt_reply.authorization_data = 0; /* * Check the preauthentication if it is there. */ if (request->padata) { errcode = check_padata(kdc_context, &client, req_pkt, request, &enc_tkt_reply, &pa_context, &e_data); if (errcode) { #ifdef KRBCONF_KDC_MODIFIES_KDB /* * Note: this doesn't work if you're using slave servers!!! * It also causes the database to be modified (and thus * need to be locked) frequently. */ if (client.fail_auth_count < KRB5_MAX_FAIL_COUNT) { client.fail_auth_count = client.fail_auth_count + 1; if (client.fail_auth_count == KRB5_MAX_FAIL_COUNT) { client.attributes |= KRB5_KDB_DISALLOW_ALL_TIX; } } client.last_failed = kdc_time; update_client = 1; #endif status = "PREAUTH_FAILED"; #ifdef KRBCONF_VAGUE_ERRORS errcode = KRB5KRB_ERR_GENERIC; #endif goto errout; } } /* * Final check before handing out ticket: If the client requires * preauthentication, verify that the proper kind of * preauthentication was carried out. */ status = missing_required_preauth(&client, &server, &enc_tkt_reply); if (status) { errcode = KRB5KDC_ERR_PREAUTH_REQUIRED; get_preauth_hint_list(request, &client, &server, &e_data); goto errout; } ticket_reply.enc_part2 = &enc_tkt_reply; /* * 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 errout; } /* 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 errout; } errcode = krb5_encrypt_tkt_part(kdc_context, &encrypting_key, &ticket_reply); krb5_free_keyblock_contents(kdc_context, &encrypting_key); encrypting_key.contents = 0; if (errcode) { status = "ENCRYPTING_TICKET"; goto errout; } ticket_reply.enc_part.kvno = server_key->key_data_kvno; /* * Find the appropriate client key. We search in the order specified * by request keytype list. */ client_key = (krb5_key_data *) NULL; for (i = 0; i < request->nktypes; i++) { useenctype = request->ktype[i]; if (!krb5_c_valid_enctype(useenctype)) continue; if (!krb5_dbe_find_enctype(kdc_context, &client, useenctype, -1, 0, &client_key)) break; } if (!(client_key)) { /* Cannot find an appropriate key */ status = "CANT_FIND_CLIENT_KEY"; errcode = KRB5KDC_ERR_ETYPE_NOSUPP; goto errout; } /* convert client.key_data into a real key */ if ((errcode = krb5_dbekd_decrypt_key_data(kdc_context, &master_keyblock, client_key, &encrypting_key, NULL))) { status = "DECRYPT_CLIENT_KEY"; goto errout; } encrypting_key.enctype = useenctype; /* Start assembling the response */ reply.msg_type = KRB5_AS_REP; reply.client = request->client; reply.ticket = &ticket_reply; reply_encpart.session = &session_key; if ((errcode = fetch_last_req_info(&client, &reply_encpart.last_req))) { status = "FETCH_LAST_REQ"; goto errout; } reply_encpart.nonce = request->nonce; /* * Take the minimum of expiration or pw_expiration if not zero. */ if (client.expiration != 0 && client.pw_expiration != 0) etime = min(client.expiration, client.pw_expiration); else etime = client.expiration ? client.expiration : client.pw_expiration; reply_encpart.key_exp = etime; reply_encpart.flags = enc_tkt_reply.flags; reply_encpart.server = ticket_reply.server; /* copy the time fields EXCEPT for authtime; it's location is used for ktime */ reply_encpart.times = enc_tkt_reply.times; reply_encpart.times.authtime = authtime = kdc_time; reply_encpart.caddrs = enc_tkt_reply.caddrs; /* Fetch the padata info to be returned */ errcode = return_padata(kdc_context, &client, req_pkt, request, &reply, client_key, &encrypting_key, &pa_context); if (errcode) { status = "KDC_RETURN_PADATA"; goto errout; } /* now encode/encrypt the response */ reply.enc_part.enctype = encrypting_key.enctype; errcode = krb5_encode_kdc_rep(kdc_context, KRB5_AS_REP, &reply_encpart, 0, &encrypting_key, &reply, response); krb5_free_keyblock_contents(kdc_context, &encrypting_key); encrypting_key.contents = 0; reply.enc_part.kvno = client_key->key_data_kvno; if (errcode) { status = "ENCODE_KDC_REP"; goto errout; } /* 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); /* SUNW14resync: * The third argument to audit_krb5kdc_as_req() is zero as the local * portnumber is no longer passed to process_as_req(). */ audit_krb5kdc_as_req(&from_in4, (in_port_t)from->port, 0, cname, sname, 0); rep_etypes2str(rep_etypestr, sizeof(rep_etypestr), &reply); krb5_klog_syslog(LOG_INFO, "AS_REQ (%s) %s: ISSUE: authtime %d, " "%s, %s for %s", ktypestr, fromstring, authtime, rep_etypestr, cname, sname); #ifdef KRBCONF_KDC_MODIFIES_KDB /* * If we get this far, we successfully did the AS_REQ. */ client.last_success = kdc_time; client.fail_auth_count = 0; update_client = 1; #endif /* KRBCONF_KDC_MODIFIES_KDB */ errout: if (pa_context) free_padata_context(kdc_context, &pa_context); if (status) { const char *emsg = NULL; if (errcode) emsg = krb5_get_error_message (kdc_context, errcode); audit_krb5kdc_as_req(&from_in4, (in_port_t)from->port, 0, cname, sname, errcode); krb5_klog_syslog(LOG_INFO, "AS_REQ (%s) %s: %s: %s for %s%s%s", ktypestr, fromstring, status, cname ? cname : "", sname ? sname : "", 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; errcode = prepare_error_as(request, errcode, &e_data, response, status); if (got_err) { krb5_free_error_message (kdc_context, status); status = 0; } } if (enc_tkt_reply.authorization_data != NULL) krb5_free_authdata(kdc_context, enc_tkt_reply.authorization_data); if (encrypting_key.contents) krb5_free_keyblock_contents(kdc_context, &encrypting_key); if (reply.padata) krb5_free_pa_data(kdc_context, reply.padata); if (cname) free(cname); if (sname) free(sname); if (c_nprincs) { #ifdef KRBCONF_KDC_MODIFIES_KDB if (update_client) { krb5_db_put_principal(kdc_context, &client, &c_nprincs); /* * ptooey. We want krb5_db_sync() or something like that. */ krb5_db_fini(kdc_context); if (kdc_active_realm->realm_dbname) krb5_db_set_name(kdc_active_realm->realm_context, kdc_active_realm->realm_dbname); krb5_db_init(kdc_context); /* Reset master key */ krb5_db_set_mkey(kdc_context, &kdc_active_realm->realm_mkey); } #endif /* KRBCONF_KDC_MODIFIES_KDB */ krb5_db_free_principal(kdc_context, &client, c_nprincs); } if (s_nprincs) krb5_db_free_principal(kdc_context, &server, s_nprincs); if (session_key.contents) krb5_free_keyblock_contents(kdc_context, &session_key); if (ticket_reply.enc_part.ciphertext.data) { memset(ticket_reply.enc_part.ciphertext.data , 0, ticket_reply.enc_part.ciphertext.length); free(ticket_reply.enc_part.ciphertext.data); } krb5_free_data_contents(kdc_context, &e_data); return errcode; } static krb5_error_code prepare_error_as (krb5_kdc_req *request, int error, krb5_data *e_data, krb5_data **response, const char *status) { krb5_error errpkt; krb5_error_code retval; krb5_data *scratch; errpkt.ctime = request->nonce; errpkt.cusec = 0; if ((retval = krb5_us_timeofday(kdc_context, &errpkt.stime, &errpkt.susec))) return(retval); errpkt.error = error; errpkt.server = request->server; errpkt.client = request->client; errpkt.text.length = strlen(status)+1; if (!(errpkt.text.data = malloc(errpkt.text.length))) return ENOMEM; (void) strcpy(errpkt.text.data, status); if (!(scratch = (krb5_data *)malloc(sizeof(*scratch)))) { free(errpkt.text.data); return ENOMEM; } if (e_data && e_data->data) { errpkt.e_data = *e_data; } else { errpkt.e_data.length = 0; errpkt.e_data.data = 0; } retval = krb5_mk_error(kdc_context, &errpkt, scratch); free(errpkt.text.data); if (retval) free(scratch); else *response = scratch; return retval; } /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * kdc/do_tgs_req.c * * Copyright 1990,1991,2001 by the Massachusetts Institute of Technology. * All Rights Reserved. * * Export of this software from the United States of America may * require a specific license from the United States Government. * It is the responsibility of any person or organization contemplating * export to obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of M.I.T. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. Furthermore if you modify this software you must label * your software as modified software and not distribute it in such a * fashion that it might be confused with the original M.I.T. software. * M.I.T. makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. * * * KDC Routines to deal with TGS_REQ's */ #include "k5-int.h" #include "com_err.h" #include #ifdef HAVE_NETINET_IN_H #include #include #ifndef hpux #include #endif #endif #include "kdc_util.h" #include "policy.h" #include "extern.h" #include "adm_proto.h" extern krb5_error_code setup_server_realm(krb5_principal); static void find_alternate_tgs (krb5_kdc_req *, krb5_db_entry *, krb5_boolean *, int *, const krb5_fulladdr *from, char *cname); static krb5_error_code prepare_error_tgs (krb5_kdc_req *, krb5_ticket *, int, const char *, krb5_data **, const char *); /*ARGSUSED*/ krb5_error_code process_tgs_req(krb5_data *pkt, const krb5_fulladdr *from, krb5_data **response) { krb5_keyblock * subkey; 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]; long long tmp_server_times, tmp_realm_times; (void) memset(&encrypting_key, 0, sizeof(krb5_keyblock)); (void) memset(&session_key, 0, sizeof(krb5_keyblock)); retval = decode_krb5_tgs_req(pkt, &request); if (retval) return retval; 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))) return retval; fromstring = inet_ntop(ADDRTYPE2FAMILY(from->address->addrtype), from->address->contents, fromstringbuf, sizeof(fromstringbuf)); if (!fromstring) fromstring = ""; 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 = krb5_db_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 || server_1->length != tgs_1->length || memcmp(server_1->data, tgs_1->data, tgs_1->length)) { krb5_db_free_principal(kdc_context, &server, nprincs); find_alternate_tgs(request, &server, &more, &nprincs, from, cname); 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); 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); 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; /* SUNW */ tmp_server_times = (long long) enc_tkt_reply.times.starttime + server.max_life; tmp_realm_times = (long long) enc_tkt_reply.times.starttime + max_life_for_realm; enc_tkt_reply.times.endtime = min(until, min(tmp_server_times, min(tmp_realm_times, min(header_ticket->enc_part2->times.endtime, KRB5_KDB_EXPIRATION)))); /* SUNW */ /* 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, min(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, min(KRB5_KDB_EXPIRATION, 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); tmp_realm_times = (long long) enc_tkt_reply.times.starttime + min(server.max_renewable_life,max_renewable_life_for_realm); enc_tkt_reply.times.renew_till = min(rtime, min(header_ticket->enc_part2->times.renew_till, min (tmp_realm_times, KRB5_KDB_EXPIRATION))); } 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 : "", sname ? sname : "", 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 : "", sname ? sname : "", 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); audit_krb5kdc_tgs_req_2ndtktmm( (struct in_addr *)from->address->contents, (in_port_t)from->port, 0, cname, sname); krb5_klog_syslog(LOG_INFO, "TGS_REQ %s: 2ND_TKT_MISMATCH: " "authtime %d, %s for %s, 2nd tkt client %s", fromstring, authtime, cname ? cname : "", sname ? sname : "", tmp ? tmp : ""); 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"; } if (ticket_reply.enc_part.ciphertext.data) { memset(ticket_reply.enc_part.ciphertext.data, 0, ticket_reply.enc_part.ciphertext.length); free(ticket_reply.enc_part.ciphertext.data); ticket_reply.enc_part.ciphertext.data = NULL; } /* 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... */ if (reply.enc_part.ciphertext.data) { memset(reply.enc_part.ciphertext.data, 0, reply.enc_part.ciphertext.length); free(reply.enc_part.ciphertext.data); reply.enc_part.ciphertext.data = NULL; } cleanup: if (status) { const char * emsg = NULL; audit_krb5kdc_tgs_req((struct in_addr *)from->address->contents, (in_port_t)from->port, 0, cname ? cname : "", sname ? sname : "", errcode); 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 : "", sname ? sname : "", 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); return retval; } static krb5_error_code prepare_error_tgs (krb5_kdc_req *request, krb5_ticket *ticket, int error, const char *ident, krb5_data **response, const char *status) { krb5_error errpkt; krb5_error_code retval; krb5_data *scratch; errpkt.ctime = request->nonce; errpkt.cusec = 0; if ((retval = krb5_us_timeofday(kdc_context, &errpkt.stime, &errpkt.susec))) return(retval); errpkt.error = error; errpkt.server = request->server; if (ticket && ticket->enc_part2) errpkt.client = ticket->enc_part2->client; else errpkt.client = 0; errpkt.text.length = strlen(status) + 1; if (!(errpkt.text.data = malloc(errpkt.text.length))) return ENOMEM; (void) strcpy(errpkt.text.data, status); if (!(scratch = (krb5_data *)malloc(sizeof(*scratch)))) { free(errpkt.text.data); return ENOMEM; } errpkt.e_data.length = 0; errpkt.e_data.data = 0; retval = krb5_mk_error(kdc_context, &errpkt, scratch); free(errpkt.text.data); if (retval) free(scratch); else *response = scratch; 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, const krb5_fulladdr *from, char *cname) { 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 = krb5_db_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; char *sname; 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; if (krb5_unparse_name(kdc_context, request->server, &sname)) { audit_krb5kdc_tgs_req_alt_tgt( (struct in_addr *)from->address->contents, (in_port_t)from->port, 0, cname, "", 0); krb5_klog_syslog(LOG_INFO, "TGS_REQ: issuing alternate TGT"); } else { limit_string(sname); audit_krb5kdc_tgs_req_alt_tgt( (struct in_addr *)from->address->contents, (in_port_t)from->port, 0, cname, sname, 0); krb5_klog_syslog(LOG_INFO, "TGS_REQ: issuing TGT %s", sname); free(sname); } return; } krb5_db_free_principal(kdc_context, server, *nprincs); continue; } *nprincs = 0; *more = FALSE; krb5_free_realm_tree(kdc_context, plist); return; } /* * kdc/extern.c * * Copyright 1990 by the Massachusetts Institute of Technology. * * Export of this software from the United States of America may * require a specific license from the United States Government. * It is the responsibility of any person or organization contemplating * export to obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of M.I.T. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. Furthermore if you modify this software you must label * your software as modified software and not distribute it in such a * fashion that it might be confused with the original M.I.T. software. * M.I.T. makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. * * * allocations of extern stuff */ #include "k5-int.h" #include "extern.h" /* real declarations of KDC's externs */ kdc_realm_t **kdc_realmlist = (kdc_realm_t **) NULL; int kdc_numrealms = 0; kdc_realm_t *kdc_active_realm = (kdc_realm_t *) NULL; krb5_data empty_string = {0, 0, ""}; krb5_timestamp kdc_infinity = KRB5_KDB_EXPIRATION; krb5_rcache kdc_rcache = (krb5_rcache) NULL; krb5_keyblock psr_key; volatile int signal_requests_exit = 0; /* gets set when signal hits */ volatile int signal_requests_hup = 0; /* ditto */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef __KRB5_KDC_EXTERN__ #define __KRB5_KDC_EXTERN__ #include #ifdef __cplusplus extern "C" { #endif /* * Copyright 1990,2001 by the Massachusetts Institute of Technology. * * Export of this software from the United States of America may * require a specific license from the United States Government. * It is the responsibility of any person or organization contemplating * export to obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of M.I.T. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. Furthermore if you modify this software you must label * your software as modified software and not distribute it in such a * fashion that it might be confused with the original M.I.T. software. * M.I.T. makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. * * * <<< Description >>> */ typedef struct __kdc_realm_data { /* * General Kerberos per-realm data. */ char * realm_name; /* Realm name */ /* XXX the real context should go away once the db_context is done. * The db_context is then associated with the realm keytab using * krb5_ktkdb_resolv(). There should be nothing in the context which * cannot span multiple realms -- proven */ krb5_context realm_context; /* Context to be used for realm */ krb5_keytab realm_keytab; /* keytab to be used for this realm */ char * realm_profile; /* Profile file for this realm */ /* * Database per-realm data. */ char * realm_dbname; /* Database name for realm */ char * realm_stash; /* Stash file name for realm */ char * realm_mpname; /* Master principal name for realm */ krb5_principal realm_mprinc; /* Master principal for realm */ krb5_keyblock realm_mkey; /* Master key for this realm */ /* * TGS per-realm data. */ krb5_principal realm_tgsprinc; /* TGS principal for this realm */ /* * Other per-realm data. */ char *realm_ports; /* Per-realm KDC UDP port */ char *realm_tcp_ports; /* Per-realm KDC TCP port */ /* * Per-realm parameters. */ krb5_deltat realm_maxlife; /* Maximum ticket life for realm */ krb5_deltat realm_maxrlife; /* Maximum renewable life for realm */ krb5_boolean realm_reject_bad_transit; /* Accept unverifiable transited_realm ? */ } kdc_realm_t; extern kdc_realm_t **kdc_realmlist; extern int kdc_numrealms; extern kdc_realm_t *kdc_active_realm; kdc_realm_t *find_realm_data (char *, krb5_ui_4); /* * Replace previously used global variables with the active (e.g. request's) * realm data. This allows us to support multiple realms with minimal logic * changes. */ #define kdc_context kdc_active_realm->realm_context #define max_life_for_realm kdc_active_realm->realm_maxlife #define max_renewable_life_for_realm kdc_active_realm->realm_maxrlife #define master_keyblock kdc_active_realm->realm_mkey #define master_princ kdc_active_realm->realm_mprinc #define tgs_server_struct *(kdc_active_realm->realm_tgsprinc) #define tgs_server kdc_active_realm->realm_tgsprinc #define dbm_db_name kdc_active_realm->realm_dbname #define primary_port kdc_active_realm->realm_pport #define reject_bad_transit kdc_active_realm->realm_reject_bad_transit /* various externs for KDC */ extern krb5_data empty_string; /* an empty string */ extern krb5_timestamp kdc_infinity; /* greater than all other timestamps */ extern krb5_rcache kdc_rcache; /* replay cache */ extern krb5_keyblock psr_key; /* key for predicted sam response */ extern volatile int signal_requests_exit; extern volatile int signal_requests_hup; /* libbsm */ extern void audit_krb5kdc_as_req(struct in_addr *, in_port_t, in_port_t, char *, char *, int); extern void audit_krb5kdc_tgs_req(struct in_addr *, in_port_t, in_port_t, char *, char *, int); extern void audit_krb5kdc_tgs_req_2ndtktmm(struct in_addr *, in_port_t, in_port_t, char *, char *); extern void audit_krb5kdc_tgs_req_alt_tgt(struct in_addr *, in_port_t, in_port_t, char *, char *, int); #ifdef __cplusplus } #endif #endif /* !__KRB5_KDC_EXTERN__ */ /* * Copyright (c) 1997-2000 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _KDC5_ERR_H #define _KDC5_ERR_H #ifdef __cplusplus extern "C" { #endif /* * kdc5_err.h: * This file is automatically generated; please do not edit it. */ #define KDC5_RCSID (-1779992064L) #define KDC5_NOPORT (-1779992063L) #define KDC5_NONET (-1779992062L) #define KDC5_IO_RESPONSE (-1779992061L) #define ERROR_TABLE_BASE_kdc5 (-1779992064L) /* for compatibility with older versions... */ #define kdc5_err_base ERROR_TABLE_BASE_kdc5 #ifdef __cplusplus } #endif #endif /* !_KDC5_ERR_H */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * kdc/kdc_preauth.c * * Copyright 1995, 2003 by the Massachusetts Institute of Technology. * All Rights Reserved. * * Export of this software from the United States of America may * require a specific license from the United States Government. * It is the responsibility of any person or organization contemplating * export to obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of M.I.T. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. Furthermore if you modify this software you must label * your software as modified software and not distribute it in such a * fashion that it might be confused with the original M.I.T. software. * M.I.T. makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. * * Preauthentication routines for the KDC. */ /* * Copyright (C) 1998 by the FundsXpress, INC. * * All rights reserved. * * Export of this software from the United States of America may require * a specific license from the United States Government. It is the * responsibility of any person or organization contemplating export to * obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of FundsXpress. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. FundsXpress makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include "k5-int.h" #include "kdc_util.h" #include "extern.h" #include "com_err.h" #include #include #include "adm_proto.h" #include #include #include #include "preauth_plugin.h" #if TARGET_OS_MAC static const char *objdirs[] = { KRB5_PLUGIN_BUNDLE_DIR, LIBDIR "/krb5/plugins/preauth", NULL }; /* should be a list */ #else static const char *objdirs[] = { LIBDIR "/krb5/plugins/preauth", NULL }; #endif /* XXX This is ugly and should be in a header file somewhere */ #ifndef KRB5INT_DES_TYPES_DEFINED #define KRB5INT_DES_TYPES_DEFINED typedef unsigned char des_cblock[8]; /* crypto-block size */ #endif typedef des_cblock mit_des_cblock; extern void mit_des_fixup_key_parity (mit_des_cblock ); extern int mit_des_is_weak_key (mit_des_cblock ); typedef struct _krb5_preauth_systems { const char *name; int type; int flags; void *plugin_context; preauth_server_init_proc init; preauth_server_fini_proc fini; preauth_server_edata_proc get_edata; preauth_server_verify_proc verify_padata; preauth_server_return_proc return_padata; preauth_server_free_reqcontext_proc free_pa_reqctx; } krb5_preauth_systems; static krb5_error_code verify_enc_timestamp (krb5_context, krb5_db_entry *client, krb5_data *req_pkt, krb5_kdc_req *request, krb5_enc_tkt_part * enc_tkt_reply, krb5_pa_data *data, preauth_get_entry_data_proc get_entry_data, void *pa_system_context, void **pa_request_context, krb5_data **e_data, krb5_authdata ***authz_data); static krb5_error_code get_etype_info (krb5_context, krb5_kdc_req *request, krb5_db_entry *client, krb5_db_entry *server, preauth_get_entry_data_proc get_entry_data, void *pa_system_context, krb5_pa_data *data); static krb5_error_code get_etype_info2(krb5_context context, krb5_kdc_req *request, krb5_db_entry *client, krb5_db_entry *server, preauth_get_entry_data_proc get_entry_data, void *pa_system_context, krb5_pa_data *pa_data); static krb5_error_code etype_info_as_rep_helper(krb5_context context, krb5_pa_data * padata, krb5_db_entry *client, krb5_kdc_req *request, krb5_kdc_rep *reply, krb5_key_data *client_key, krb5_keyblock *encrypting_key, krb5_pa_data **send_pa, int etype_info2); static krb5_error_code return_etype_info(krb5_context, krb5_pa_data * padata, krb5_db_entry *client, krb5_data *req_pkt, krb5_kdc_req *request, krb5_kdc_rep *reply, krb5_key_data *client_key, krb5_keyblock *encrypting_key, krb5_pa_data **send_pa, preauth_get_entry_data_proc get_entry_data, void *pa_system_context, void **pa_request_context); static krb5_error_code return_etype_info2(krb5_context, krb5_pa_data * padata, krb5_db_entry *client, krb5_data *req_pkt, krb5_kdc_req *request, krb5_kdc_rep *reply, krb5_key_data *client_key, krb5_keyblock *encrypting_key, krb5_pa_data **send_pa, preauth_get_entry_data_proc get_entry_data, void *pa_system_context, void **pa_request_context); static krb5_error_code return_pw_salt (krb5_context, krb5_pa_data * padata, krb5_db_entry *client, krb5_data *req_pkt, krb5_kdc_req *request, krb5_kdc_rep *reply, krb5_key_data *client_key, krb5_keyblock *encrypting_key, krb5_pa_data **send_pa, preauth_get_entry_data_proc get_entry_data, void *pa_system_context, void **pa_request_context); /* SAM preauth support */ static krb5_error_code verify_sam_response (krb5_context, krb5_db_entry *client, krb5_data *req_pkt, krb5_kdc_req *request, krb5_enc_tkt_part * enc_tkt_reply, krb5_pa_data *data, preauth_get_entry_data_proc get_entry_data, void *pa_module_context, void **pa_request_context, krb5_data **e_data, krb5_authdata ***authz_data); static krb5_error_code get_sam_edata (krb5_context, krb5_kdc_req *request, krb5_db_entry *client, krb5_db_entry *server, preauth_get_entry_data_proc get_entry_data, void *pa_module_context, krb5_pa_data *data); static krb5_error_code return_sam_data (krb5_context, krb5_pa_data * padata, krb5_db_entry *client, krb5_data *req_pkt, krb5_kdc_req *request, krb5_kdc_rep *reply, krb5_key_data *client_key, krb5_keyblock *encrypting_key, krb5_pa_data **send_pa, preauth_get_entry_data_proc get_entry_data, void *pa_module_context, void **pa_request_context); static krb5_preauth_systems static_preauth_systems[] = { { "timestamp", KRB5_PADATA_ENC_TIMESTAMP, 0, NULL, NULL, NULL, 0, verify_enc_timestamp, 0 }, { "etype-info", KRB5_PADATA_ETYPE_INFO, 0, NULL, NULL, NULL, get_etype_info, 0, return_etype_info }, { "etype-info2", KRB5_PADATA_ETYPE_INFO2, 0, NULL, NULL, NULL, get_etype_info2, 0, return_etype_info2 }, { "pw-salt", KRB5_PADATA_PW_SALT, PA_PSEUDO, /* Don't include this in the error list */ NULL, NULL, NULL, 0, 0, return_pw_salt }, { "sam-response", KRB5_PADATA_SAM_RESPONSE, 0, NULL, NULL, NULL, 0, verify_sam_response, return_sam_data }, { "sam-challenge", KRB5_PADATA_SAM_CHALLENGE, PA_HARDWARE, /* causes get_preauth_hint_list to use this */ NULL, NULL, NULL, get_sam_edata, 0, 0 }, { "[end]", -1,} }; static krb5_preauth_systems *preauth_systems; static int n_preauth_systems; static struct plugin_dir_handle preauth_plugins; krb5_error_code load_preauth_plugins(krb5_context context) { struct errinfo err; void **preauth_plugins_ftables; struct krb5plugin_preauth_server_ftable_v1 *ftable; int module_count, i, j, k; void *plugin_context; preauth_server_init_proc server_init_proc = NULL; char **kdc_realm_names = NULL; memset(&err, 0, sizeof(err)); /* Attempt to load all of the preauth plugins we can find. */ PLUGIN_DIR_INIT(&preauth_plugins); if (PLUGIN_DIR_OPEN(&preauth_plugins) == 0) { if (krb5int_open_plugin_dirs(objdirs, NULL, &preauth_plugins, &err) != 0) { return KRB5_PLUGIN_NO_HANDLE; } } /* Get the method tables provided by the loaded plugins. */ preauth_plugins_ftables = NULL; if (krb5int_get_plugin_dir_data(&preauth_plugins, "preauthentication_server_1", &preauth_plugins_ftables, &err) != 0) { return KRB5_PLUGIN_NO_HANDLE; } /* Count the valid modules. */ module_count = sizeof(static_preauth_systems) / sizeof(static_preauth_systems[0]); if (preauth_plugins_ftables != NULL) { for (i = 0; preauth_plugins_ftables[i] != NULL; i++) { ftable = preauth_plugins_ftables[i]; if ((ftable->flags_proc == NULL) && (ftable->edata_proc == NULL) && (ftable->verify_proc == NULL) && (ftable->return_proc == NULL)) { continue; } for (j = 0; ftable->pa_type_list != NULL && ftable->pa_type_list[j] > 0; j++) { module_count++; } } } /* Build the complete list of supported preauthentication options, and * leave room for a terminator entry. */ preauth_systems = malloc(sizeof(krb5_preauth_systems) * (module_count + 1)); if (preauth_systems == NULL) { krb5int_free_plugin_dir_data(preauth_plugins_ftables); return ENOMEM; } /* Build a list of the names of the supported realms for this KDC. * The list of names is terminated with a NULL. */ kdc_realm_names = malloc(sizeof(char *) * (kdc_numrealms + 1)); if (kdc_realm_names == NULL) { krb5int_free_plugin_dir_data(preauth_plugins_ftables); return ENOMEM; } for (i = 0; i < kdc_numrealms; i++) { kdc_realm_names[i] = kdc_realmlist[i]->realm_name; } kdc_realm_names[i] = NULL; /* Add the locally-supplied mechanisms to the dynamic list first. */ for (i = 0, k = 0; i < sizeof(static_preauth_systems) / sizeof(static_preauth_systems[0]); i++) { if (static_preauth_systems[i].type == -1) break; preauth_systems[k] = static_preauth_systems[i]; /* Try to initialize the preauth system. If it fails, we'll remove it * from the list of systems we'll be using. */ plugin_context = NULL; server_init_proc = static_preauth_systems[i].init; if ((server_init_proc != NULL) && ((*server_init_proc)(context, &plugin_context, (const char **)kdc_realm_names) != 0)) { memset(&preauth_systems[k], 0, sizeof(preauth_systems[k])); continue; } preauth_systems[k].plugin_context = plugin_context; k++; } /* Now add the dynamically-loaded mechanisms to the list. */ if (preauth_plugins_ftables != NULL) { for (i = 0; preauth_plugins_ftables[i] != NULL; i++) { ftable = preauth_plugins_ftables[i]; if ((ftable->flags_proc == NULL) && (ftable->edata_proc == NULL) && (ftable->verify_proc == NULL) && (ftable->return_proc == NULL)) { continue; } plugin_context = NULL; for (j = 0; ftable->pa_type_list != NULL && ftable->pa_type_list[j] > 0; j++) { /* Try to initialize the plugin. If it fails, we'll remove it * from the list of modules we'll be using. */ if (j == 0) { server_init_proc = ftable->init_proc; if (server_init_proc != NULL) { krb5_error_code initerr; initerr = (*server_init_proc)(context, &plugin_context, (const char **)kdc_realm_names); if (initerr) { const char *emsg; emsg = krb5_get_error_message(context, initerr); if (emsg) { krb5_klog_syslog(LOG_ERR, "preauth %s failed to initialize: %s", ftable->name, emsg); krb5_free_error_message(context, emsg); } memset(&preauth_systems[k], 0, sizeof(preauth_systems[k])); break; /* skip all modules in this plugin */ } } } preauth_systems[k].name = ftable->name; preauth_systems[k].type = ftable->pa_type_list[j]; if (ftable->flags_proc != NULL) preauth_systems[k].flags = ftable->flags_proc(context, preauth_systems[k].type); else preauth_systems[k].flags = 0; preauth_systems[k].plugin_context = plugin_context; preauth_systems[k].init = server_init_proc; /* Only call fini once for each plugin */ if (j == 0) preauth_systems[k].fini = ftable->fini_proc; else preauth_systems[k].fini = NULL; preauth_systems[k].get_edata = ftable->edata_proc; preauth_systems[k].verify_padata = ftable->verify_proc; preauth_systems[k].return_padata = ftable->return_proc; preauth_systems[k].free_pa_reqctx = ftable->freepa_reqcontext_proc; k++; } } krb5int_free_plugin_dir_data(preauth_plugins_ftables); } free(kdc_realm_names); n_preauth_systems = k; /* Add the end-of-list marker. */ preauth_systems[k].name = "[end]"; preauth_systems[k].type = -1; return 0; } krb5_error_code unload_preauth_plugins(krb5_context context) { int i; if (preauth_systems != NULL) { for (i = 0; i < n_preauth_systems; i++) { if (preauth_systems[i].fini != NULL) { (*preauth_systems[i].fini)(context, preauth_systems[i].plugin_context); } memset(&preauth_systems[i], 0, sizeof(preauth_systems[i])); } free(preauth_systems); preauth_systems = NULL; n_preauth_systems = 0; krb5int_close_plugin_dirs(&preauth_plugins); } return 0; } /* * The make_padata_context() function creates a space for storing any context * information which will be needed by return_padata() later. Each preauth * type gets a context storage location of its own. */ struct request_pa_context { int n_contexts; struct { krb5_preauth_systems *pa_system; void *pa_context; } *contexts; }; static krb5_error_code make_padata_context(krb5_context context, void **padata_context) { int i; struct request_pa_context *ret; ret = malloc(sizeof(*ret)); if (ret == NULL) { return ENOMEM; } ret->n_contexts = n_preauth_systems; ret->contexts = malloc(sizeof(ret->contexts[0]) * ret->n_contexts); if (ret->contexts == NULL) { free(ret); return ENOMEM; } memset(ret->contexts, 0, sizeof(ret->contexts[0]) * ret->n_contexts); for (i = 0; i < ret->n_contexts; i++) { ret->contexts[i].pa_system = &preauth_systems[i]; ret->contexts[i].pa_context = NULL; } *padata_context = ret; return 0; } /* * The free_padata_context function frees any context information pointers * which the check_padata() function created but which weren't already cleaned * up by return_padata(). */ krb5_error_code free_padata_context(krb5_context kcontext, void **padata_context) { struct request_pa_context *context; krb5_preauth_systems *preauth_system; void **pctx, *mctx; int i; if (padata_context == NULL) return 0; context = *padata_context; for (i = 0; i < context->n_contexts; i++) { if (context->contexts[i].pa_context != NULL) { preauth_system = context->contexts[i].pa_system; mctx = preauth_system->plugin_context; if (preauth_system->free_pa_reqctx != NULL) { pctx = &context->contexts[i].pa_context; (*preauth_system->free_pa_reqctx)(kcontext, mctx, pctx); } context->contexts[i].pa_context = NULL; } } free(context->contexts); free(context); return 0; } /* Retrieve a specified tl_data item from the given entry, and return its * contents in a new krb5_data, which must be freed by the caller. */ static krb5_error_code get_entry_tl_data(krb5_context context, krb5_db_entry *entry, krb5_int16 tl_data_type, krb5_data **result) { krb5_tl_data *tl; for (tl = entry->tl_data; tl != NULL; tl = tl->tl_data_next) { if (tl->tl_data_type == tl_data_type) { *result = malloc(sizeof(krb5_data)); if (*result == NULL) { return ENOMEM; } (*result)->magic = KV5M_DATA; (*result)->data = malloc(tl->tl_data_length); if ((*result)->data == NULL) { free(*result); *result = NULL; return ENOMEM; } memcpy((*result)->data, tl->tl_data_contents, tl->tl_data_length); return 0; } } return ENOENT; } /* * Retrieve a specific piece of information pertaining to the entry or the * request and return it in a new krb5_data item which the caller must free. * * This may require massaging data into a contrived format, but it will * hopefully keep us from having to reveal library-internal functions to * modules. */ static krb5_error_code get_entry_data(krb5_context context, krb5_kdc_req *request, krb5_db_entry *entry, krb5_int32 type, krb5_data **result) { int i, k; krb5_data *ret; krb5_deltat *delta; krb5_keyblock *keys; krb5_key_data *entry_key; switch (type) { case krb5plugin_preauth_entry_request_certificate: return get_entry_tl_data(context, entry, KRB5_TL_USER_CERTIFICATE, result); break; case krb5plugin_preauth_entry_max_time_skew: ret = malloc(sizeof(krb5_data)); if (ret == NULL) return ENOMEM; delta = malloc(sizeof(krb5_deltat)); if (delta == NULL) { free(ret); return ENOMEM; } *delta = context->clockskew; ret->data = (char *) delta; ret->length = sizeof(*delta); *result = ret; return 0; break; case krb5plugin_preauth_keys: ret = malloc(sizeof(krb5_data)); if (ret == NULL) return ENOMEM; keys = malloc(sizeof(krb5_keyblock) * (request->nktypes + 1)); if (keys == NULL) { free(ret); return ENOMEM; } ret->data = (char *) keys; ret->length = sizeof(krb5_keyblock) * (request->nktypes + 1); memset(ret->data, 0, ret->length); k = 0; for (i = 0; i < request->nktypes; i++) { entry_key = NULL; if (krb5_dbe_find_enctype(context, entry, request->ktype[i], -1, 0, &entry_key) != 0) continue; if (krb5_dbekd_decrypt_key_data(context, &master_keyblock, entry_key, &keys[k], NULL) != 0) { if (keys[k].contents != NULL) krb5_free_keyblock_contents(context, &keys[k]); memset(&keys[k], 0, sizeof(keys[k])); continue; } k++; } if (k > 0) { *result = ret; return 0; } else { free(keys); free(ret); } break; case krb5plugin_preauth_request_body: ret = NULL; encode_krb5_kdc_req_body(request, &ret); if (ret != NULL) { *result = ret; return 0; } return ASN1_PARSE_ERROR; break; default: break; } return ENOENT; } static krb5_error_code find_pa_system(int type, krb5_preauth_systems **preauth) { krb5_preauth_systems *ap; ap = preauth_systems ? preauth_systems : static_preauth_systems; while ((ap->type != -1) && (ap->type != type)) ap++; if (ap->type == -1) return(KRB5_PREAUTH_BAD_TYPE); *preauth = ap; return 0; } static krb5_error_code find_pa_context(krb5_preauth_systems *pa_sys, struct request_pa_context *context, void ***pa_context) { int i; *pa_context = 0; if (context == NULL) return KRB5KRB_ERR_GENERIC; for (i = 0; i < context->n_contexts; i++) { if (context->contexts[i].pa_system == pa_sys) { *pa_context = &context->contexts[i].pa_context; return 0; } } return KRB5KRB_ERR_GENERIC; } /* * Create a list of indices into the preauth_systems array, sorted by order of * preference. */ static krb5_boolean pa_list_includes(krb5_pa_data **pa_data, krb5_preauthtype pa_type) { while (*pa_data != NULL) { if ((*pa_data)->pa_type == pa_type) return TRUE; pa_data++; } return FALSE; } static void sort_pa_order(krb5_context context, krb5_kdc_req *request, int *pa_order) { int i, j, k, n_repliers, n_key_replacers; /* First, set up the default order. */ i = 0; for (j = 0; j < n_preauth_systems; j++) { if (preauth_systems[j].return_padata != NULL) pa_order[i++] = j; } n_repliers = i; pa_order[n_repliers] = -1; /* Reorder so that PA_REPLACES_KEY modules are listed first. */ for (i = 0; i < n_repliers; i++) { /* If this module replaces the key, then it's okay to leave it where it * is in the order. */ if (preauth_systems[pa_order[i]].flags & PA_REPLACES_KEY) continue; /* If not, search for a module which does, and swap in the first one we * find. */ for (j = i + 1; j < n_repliers; j++) { if (preauth_systems[pa_order[j]].flags & PA_REPLACES_KEY) { k = pa_order[j]; pa_order[j] = pa_order[i]; pa_order[i] = k; break; } } } if (request->padata != NULL) { /* Now reorder the subset of modules which replace the key, * bubbling those which handle pa_data types provided by the * client ahead of the others. */ for (i = 0; preauth_systems[pa_order[i]].flags & PA_REPLACES_KEY; i++) { continue; } n_key_replacers = i; for (i = 0; i < n_key_replacers; i++) { if (pa_list_includes(request->padata, preauth_systems[pa_order[i]].type)) continue; for (j = i + 1; j < n_key_replacers; j++) { if (pa_list_includes(request->padata, preauth_systems[pa_order[j]].type)) { k = pa_order[j]; pa_order[j] = pa_order[i]; pa_order[i] = k; break; } } } } #ifdef DEBUG krb5_klog_syslog(LOG_DEBUG, "original preauth mechanism list:"); for (i = 0; i < n_preauth_systems; i++) { if (preauth_systems[i].return_padata != NULL) krb5_klog_syslog(LOG_DEBUG, "... %s(%d)", preauth_systems[i].name, preauth_systems[i].type); } krb5_klog_syslog(LOG_DEBUG, "sorted preauth mechanism list:"); for (i = 0; pa_order[i] != -1; i++) { krb5_klog_syslog(LOG_DEBUG, "... %s(%d)", preauth_systems[pa_order[i]].name, preauth_systems[pa_order[i]].type); } #endif } const char *missing_required_preauth(krb5_db_entry *client, krb5_db_entry *server, krb5_enc_tkt_part *enc_tkt_reply) { #if 0 /* * If this is the pwchange service, and the pre-auth bit is set, * allow it even if the HW preauth would normally be required. * * Sandia national labs wanted this for some strange reason... we * leave it disabled normally. */ if (isflagset(server->attributes, KRB5_KDB_PWCHANGE_SERVICE) && isflagset(enc_tkt_reply->flags, TKT_FLG_PRE_AUTH)) return 0; #endif #ifdef DEBUG krb5_klog_syslog (LOG_DEBUG, "client needs %spreauth, %shw preauth; request has %spreauth, %shw preauth", isflagset (client->attributes, KRB5_KDB_REQUIRES_PRE_AUTH) ? "" : "no ", isflagset (client->attributes, KRB5_KDB_REQUIRES_HW_AUTH) ? "" : "no ", isflagset (enc_tkt_reply->flags, TKT_FLG_PRE_AUTH) ? "" : "no ", isflagset (enc_tkt_reply->flags, TKT_FLG_HW_AUTH) ? "" : "no "); #endif if (isflagset(client->attributes, KRB5_KDB_REQUIRES_PRE_AUTH) && !isflagset(enc_tkt_reply->flags, TKT_FLG_PRE_AUTH)) return "NEEDED_PREAUTH"; if (isflagset(client->attributes, KRB5_KDB_REQUIRES_HW_AUTH) && !isflagset(enc_tkt_reply->flags, TKT_FLG_HW_AUTH)) return "NEEDED_HW_PREAUTH"; return 0; } void get_preauth_hint_list(krb5_kdc_req *request, krb5_db_entry *client, krb5_db_entry *server, krb5_data *e_data) { int hw_only; krb5_preauth_systems *ap; krb5_pa_data **pa_data, **pa; krb5_data *edat; krb5_error_code retval; /* Zero these out in case we need to abort */ e_data->length = 0; e_data->data = 0; hw_only = isflagset(client->attributes, KRB5_KDB_REQUIRES_HW_AUTH); pa_data = malloc(sizeof(krb5_pa_data *) * (n_preauth_systems+1)); if (pa_data == 0) return; memset(pa_data, 0, sizeof(krb5_pa_data *) * (n_preauth_systems+1)); pa = pa_data; for (ap = preauth_systems; ap->type != -1; ap++) { if (hw_only && !(ap->flags & PA_HARDWARE)) continue; if (ap->flags & PA_PSEUDO) continue; *pa = malloc(sizeof(krb5_pa_data)); if (*pa == 0) goto errout; memset(*pa, 0, sizeof(krb5_pa_data)); (*pa)->magic = KV5M_PA_DATA; (*pa)->pa_type = ap->type; if (ap->get_edata) { retval = (ap->get_edata)(kdc_context, request, client, server, get_entry_data, ap->plugin_context, *pa); if (retval) { /* just failed on this type, continue */ free(*pa); *pa = 0; continue; } } pa++; } if (pa_data[0] == 0) { krb5_klog_syslog (LOG_INFO, "%spreauth required but hint list is empty", hw_only ? "hw" : ""); } retval = encode_krb5_padata_sequence((krb5_pa_data * const *) pa_data, &edat); if (retval) goto errout; *e_data = *edat; free(edat); errout: krb5_free_pa_data(kdc_context, pa_data); return; } /* * Add authorization data returned from preauth modules to the ticket * It is assumed that ad is a "null-terminated" array of krb5_authdata ptrs */ static krb5_error_code add_authorization_data(krb5_enc_tkt_part *enc_tkt_part, krb5_authdata **ad) { krb5_authdata **newad; int oldones, newones; int i; if (enc_tkt_part == NULL || ad == NULL) return EINVAL; for (newones = 0; ad[newones] != NULL; newones++); if (newones == 0) return 0; /* nothing to add */ if (enc_tkt_part->authorization_data == NULL) oldones = 0; else for (oldones = 0; enc_tkt_part->authorization_data[oldones] != NULL; oldones++); newad = malloc((oldones + newones + 1) * sizeof(krb5_authdata *)); if (newad == NULL) return ENOMEM; /* Copy any existing pointers */ for (i = 0; i < oldones; i++) newad[i] = enc_tkt_part->authorization_data[i]; /* Add the new ones */ for (i = 0; i < newones; i++) newad[oldones+i] = ad[i]; /* Terminate the new list */ newad[oldones+i] = NULL; /* Free any existing list */ if (enc_tkt_part->authorization_data != NULL) free(enc_tkt_part->authorization_data); /* Install our new list */ enc_tkt_part->authorization_data = newad; return 0; } /* * This routine is called to verify the preauthentication information * for a V5 request. * * Returns 0 if the pre-authentication is valid, non-zero to indicate * an error code of some sort. */ krb5_error_code check_padata (krb5_context context, krb5_db_entry *client, krb5_data *req_pkt, krb5_kdc_req *request, krb5_enc_tkt_part *enc_tkt_reply, void **padata_context, krb5_data *e_data) { krb5_error_code retval = 0; krb5_pa_data **padata; krb5_preauth_systems *pa_sys; void **pa_context; krb5_data *pa_e_data = NULL, *tmp_e_data = NULL; int pa_ok = 0, pa_found = 0; krb5_error_code saved_retval = 0; int use_saved_retval = 0; const char *emsg; krb5_authdata **tmp_authz_data = NULL; if (request->padata == 0) return 0; if (make_padata_context(context, padata_context) != 0) { return KRB5KRB_ERR_GENERIC; } #ifdef DEBUG krb5_klog_syslog (LOG_DEBUG, "checking padata"); #endif for (padata = request->padata; *padata; padata++) { #ifdef DEBUG krb5_klog_syslog (LOG_DEBUG, ".. pa_type 0x%x", (*padata)->pa_type); #endif if (find_pa_system((*padata)->pa_type, &pa_sys)) continue; if (find_pa_context(pa_sys, *padata_context, &pa_context)) continue; #ifdef DEBUG krb5_klog_syslog (LOG_DEBUG, ".. pa_type %s", pa_sys->name); #endif if (pa_sys->verify_padata == 0) continue; pa_found++; retval = pa_sys->verify_padata(context, client, req_pkt, request, enc_tkt_reply, *padata, get_entry_data, pa_sys->plugin_context, pa_context, &tmp_e_data, &tmp_authz_data); if (retval) { emsg = krb5_get_error_message (context, retval); krb5_klog_syslog (LOG_INFO, "preauth (%s) verify failure: %s", pa_sys->name, emsg); krb5_free_error_message (context, emsg); /* Ignore authorization data returned from modules that fail */ if (tmp_authz_data != NULL) { krb5_free_authdata(context, tmp_authz_data); tmp_authz_data = NULL; } if (pa_sys->flags & PA_REQUIRED) { /* free up any previous edata we might have been saving */ if (pa_e_data != NULL) krb5_free_data(context, pa_e_data); pa_e_data = tmp_e_data; tmp_e_data = NULL; use_saved_retval = 0; /* Make sure we use the current retval */ pa_ok = 0; break; } /* * We'll return edata from either the first PA_REQUIRED module * that fails, or the first non-PA_REQUIRED module that fails. * Hang on to edata from the first non-PA_REQUIRED module. * If we've already got one saved, simply discard this one. */ if (tmp_e_data != NULL) { if (pa_e_data == NULL) { /* save the first error code and e-data */ pa_e_data = tmp_e_data; tmp_e_data = NULL; saved_retval = retval; use_saved_retval = 1; } else { /* discard this extra e-data from non-PA_REQUIRED module */ krb5_free_data(context, tmp_e_data); tmp_e_data = NULL; } } } else { #ifdef DEBUG krb5_klog_syslog (LOG_DEBUG, ".. .. ok"); #endif /* Ignore any edata returned on success */ if (tmp_e_data != NULL) { krb5_free_data(context, tmp_e_data); tmp_e_data = NULL; } /* Add any authorization data to the ticket */ if (tmp_authz_data != NULL) { add_authorization_data(enc_tkt_reply, tmp_authz_data); free(tmp_authz_data); tmp_authz_data = NULL; } pa_ok = 1; if (pa_sys->flags & PA_SUFFICIENT) break; } } /* Don't bother copying and returning e-data on success */ if (pa_ok && pa_e_data != NULL) { krb5_free_data(context, pa_e_data); pa_e_data = NULL; } /* Return any e-data from the preauth that caused us to exit the loop */ if (pa_e_data != NULL) { e_data->data = malloc(pa_e_data->length); if (e_data->data == NULL) { krb5_free_data(context, pa_e_data); /* Solaris Kerberos */ return ENOMEM; } memcpy(e_data->data, pa_e_data->data, pa_e_data->length); e_data->length = pa_e_data->length; krb5_free_data(context, pa_e_data); pa_e_data = NULL; if (use_saved_retval != 0) retval = saved_retval; } if (pa_ok) return 0; /* pa system was not found, but principal doesn't require preauth */ if (!pa_found && !isflagset(client->attributes, KRB5_KDB_REQUIRES_PRE_AUTH) && !isflagset(client->attributes, KRB5_KDB_REQUIRES_HW_AUTH)) return 0; if (!pa_found) { emsg = krb5_get_error_message(context, retval); krb5_klog_syslog (LOG_INFO, "no valid preauth type found: %s", emsg); krb5_free_error_message(context, emsg); } /* The following switch statement allows us * to return some preauth system errors back to the client. */ switch(retval) { case KRB5KRB_AP_ERR_BAD_INTEGRITY: case KRB5KRB_AP_ERR_SKEW: case KRB5KDC_ERR_ETYPE_NOSUPP: /* rfc 4556 */ case KRB5KDC_ERR_CLIENT_NOT_TRUSTED: case KRB5KDC_ERR_INVALID_SIG: case KRB5KDC_ERR_DH_KEY_PARAMETERS_NOT_ACCEPTED: case KRB5KDC_ERR_CANT_VERIFY_CERTIFICATE: case KRB5KDC_ERR_INVALID_CERTIFICATE: case KRB5KDC_ERR_REVOKED_CERTIFICATE: case KRB5KDC_ERR_REVOCATION_STATUS_UNKNOWN: case KRB5KDC_ERR_CLIENT_NAME_MISMATCH: case KRB5KDC_ERR_INCONSISTENT_KEY_PURPOSE: case KRB5KDC_ERR_DIGEST_IN_CERT_NOT_ACCEPTED: case KRB5KDC_ERR_PA_CHECKSUM_MUST_BE_INCLUDED: case KRB5KDC_ERR_DIGEST_IN_SIGNED_DATA_NOT_ACCEPTED: case KRB5KDC_ERR_PUBLIC_KEY_ENCRYPTION_NOT_SUPPORTED: /* earlier drafts of what became rfc 4556 */ case KRB5KDC_ERR_CERTIFICATE_MISMATCH: case KRB5KDC_ERR_KDC_NOT_TRUSTED: case KRB5KDC_ERR_REVOCATION_STATUS_UNAVAILABLE: /* This value is shared with KRB5KDC_ERR_DH_KEY_PARAMETERS_NOT_ACCEPTED. */ /* case KRB5KDC_ERR_KEY_TOO_WEAK: */ return retval; default: return KRB5KDC_ERR_PREAUTH_FAILED; } } /* * return_padata creates any necessary preauthentication * structures which should be returned by the KDC to the client */ krb5_error_code return_padata(krb5_context context, krb5_db_entry *client, krb5_data *req_pkt, krb5_kdc_req *request, krb5_kdc_rep *reply, krb5_key_data *client_key, krb5_keyblock *encrypting_key, void **padata_context) { krb5_error_code retval; krb5_pa_data ** padata; krb5_pa_data ** send_pa_list; krb5_pa_data ** send_pa; krb5_pa_data * pa = 0; krb5_preauth_systems * ap; int * pa_order; int * pa_type; int size = 0; void ** pa_context; krb5_boolean key_modified; krb5_keyblock original_key; if ((!*padata_context)&& (make_padata_context(context, padata_context) != 0)) { return KRB5KRB_ERR_GENERIC; } for (ap = preauth_systems; ap->type != -1; ap++) { if (ap->return_padata) size++; } if ((send_pa_list = malloc((size+1) * sizeof(krb5_pa_data *))) == NULL) return ENOMEM; if ((pa_order = malloc((size+1) * sizeof(int))) == NULL) { free(send_pa_list); return ENOMEM; } sort_pa_order(context, request, pa_order); retval = krb5_copy_keyblock_contents(context, encrypting_key, &original_key); if (retval) { free(send_pa_list); free(pa_order); return retval; } key_modified = FALSE; send_pa = send_pa_list; *send_pa = 0; for (pa_type = pa_order; *pa_type != -1; pa_type++) { ap = &preauth_systems[*pa_type]; if (!key_modified) if (original_key.enctype != encrypting_key->enctype) key_modified = TRUE; if (!key_modified) if (original_key.length != encrypting_key->length) key_modified = TRUE; if (!key_modified) if (memcmp(original_key.contents, encrypting_key->contents, original_key.length) != 0) key_modified = TRUE; if (key_modified && (ap->flags & PA_REPLACES_KEY)) continue; if (ap->return_padata == 0) continue; if (find_pa_context(ap, *padata_context, &pa_context)) continue; pa = 0; if (request->padata) { for (padata = request->padata; *padata; padata++) { if ((*padata)->pa_type == ap->type) { pa = *padata; break; } } } if ((retval = ap->return_padata(context, pa, client, req_pkt, request, reply, client_key, encrypting_key, send_pa, get_entry_data, ap->plugin_context, pa_context))) { goto cleanup; } if (*send_pa) send_pa++; *send_pa = 0; } retval = 0; if (send_pa_list[0]) { reply->padata = send_pa_list; send_pa_list = 0; } cleanup: if (send_pa_list) krb5_free_pa_data(context, send_pa_list); /* Solaris Kerberos */ krb5_free_keyblock_contents(context, &original_key); free(pa_order); return (retval); } static krb5_boolean enctype_requires_etype_info_2(krb5_enctype enctype) { switch(enctype) { case ENCTYPE_DES_CBC_CRC: case ENCTYPE_DES_CBC_MD4: case ENCTYPE_DES_CBC_MD5: case ENCTYPE_DES3_CBC_SHA1: case ENCTYPE_DES3_CBC_RAW: case ENCTYPE_ARCFOUR_HMAC: case ENCTYPE_ARCFOUR_HMAC_EXP : return 0; default: if (krb5_c_valid_enctype(enctype)) return 1; else return 0; } } static krb5_boolean request_contains_enctype (krb5_context context, const krb5_kdc_req *request, krb5_enctype enctype) { int i; for (i =0; i < request->nktypes; i++) if (request->ktype[i] == enctype) return 1; return 0; } static krb5_error_code verify_enc_timestamp(krb5_context context, krb5_db_entry *client, krb5_data *req_pkt, krb5_kdc_req *request, krb5_enc_tkt_part *enc_tkt_reply, krb5_pa_data *pa, preauth_get_entry_data_proc ets_get_entry_data, void *pa_system_context, void **pa_request_context, krb5_data **e_data, krb5_authdata ***authz_data) { krb5_pa_enc_ts * pa_enc = 0; krb5_error_code retval; krb5_data scratch; krb5_data enc_ts_data; krb5_enc_data *enc_data = 0; krb5_keyblock key; krb5_key_data * client_key; krb5_int32 start; krb5_timestamp timenow; krb5_error_code decrypt_err = 0; (void) memset(&key, 0, sizeof(krb5_keyblock)); scratch.data = (char *) pa->contents; scratch.length = pa->length; enc_ts_data.data = 0; if ((retval = decode_krb5_enc_data(&scratch, &enc_data)) != 0) goto cleanup; enc_ts_data.length = enc_data->ciphertext.length; if ((enc_ts_data.data = (char *) malloc(enc_ts_data.length)) == NULL) goto cleanup; start = 0; decrypt_err = 0; while (1) { if ((retval = krb5_dbe_search_enctype(context, client, &start, enc_data->enctype, -1, 0, &client_key))) goto cleanup; if ((retval = krb5_dbekd_decrypt_key_data(context, &master_keyblock, client_key, &key, NULL))) goto cleanup; key.enctype = enc_data->enctype; retval = krb5_c_decrypt(context, &key, KRB5_KEYUSAGE_AS_REQ_PA_ENC_TS, 0, enc_data, &enc_ts_data); krb5_free_keyblock_contents(context, &key); if (retval == 0) break; else decrypt_err = retval; } if ((retval = decode_krb5_pa_enc_ts(&enc_ts_data, &pa_enc)) != 0) goto cleanup; if ((retval = krb5_timeofday(context, &timenow)) != 0) goto cleanup; if (labs(timenow - pa_enc->patimestamp) > context->clockskew) { retval = KRB5KRB_AP_ERR_SKEW; goto cleanup; } setflag(enc_tkt_reply->flags, TKT_FLG_PRE_AUTH); retval = 0; cleanup: if (enc_data) { krb5_free_data_contents(context, &enc_data->ciphertext); free(enc_data); } krb5_free_data_contents(context, &enc_ts_data); if (pa_enc) free(pa_enc); /* * If we get NO_MATCHING_KEY and decryption previously failed, and * we failed to find any other keys of the correct enctype after * that failed decryption, it probably means that the password was * incorrect. */ if (retval == KRB5_KDB_NO_MATCHING_KEY && decrypt_err != 0) retval = decrypt_err; return retval; } static krb5_error_code _make_etype_info_entry(krb5_context context, krb5_kdc_req *request, krb5_key_data *client_key, krb5_enctype etype, krb5_etype_info_entry **entry, int etype_info2) { krb5_data salt; krb5_etype_info_entry * tmp_entry; krb5_error_code retval; if ((tmp_entry = malloc(sizeof(krb5_etype_info_entry))) == NULL) return ENOMEM; salt.data = 0; tmp_entry->magic = KV5M_ETYPE_INFO_ENTRY; tmp_entry->etype = etype; tmp_entry->length = KRB5_ETYPE_NO_SALT; tmp_entry->salt = 0; tmp_entry->s2kparams.data = NULL; tmp_entry->s2kparams.length = 0; retval = get_salt_from_key(context, request->client, client_key, &salt); if (retval) goto fail; if (etype_info2 && client_key->key_data_ver > 1 && client_key->key_data_type[1] == KRB5_KDB_SALTTYPE_AFS3) { switch (etype) { case ENCTYPE_DES_CBC_CRC: case ENCTYPE_DES_CBC_MD4: case ENCTYPE_DES_CBC_MD5: tmp_entry->s2kparams.data = malloc(1); if (tmp_entry->s2kparams.data == NULL) { retval = ENOMEM; goto fail; } tmp_entry->s2kparams.length = 1; tmp_entry->s2kparams.data[0] = 1; break; default: break; } } if (salt.length >= 0) { tmp_entry->length = salt.length; tmp_entry->salt = (unsigned char *) salt.data; salt.data = 0; } *entry = tmp_entry; return 0; fail: if (tmp_entry) { if (tmp_entry->s2kparams.data) free(tmp_entry->s2kparams.data); free(tmp_entry); } if (salt.data) free(salt.data); return retval; } /* * This function returns the etype information for a particular * client, to be passed back in the preauth list in the KRB_ERROR * message. It supports generating both etype_info and etype_info2 * as most of the work is the same. */ static krb5_error_code etype_info_helper(krb5_context context, krb5_kdc_req *request, krb5_db_entry *client, krb5_db_entry *server, krb5_pa_data *pa_data, int etype_info2) { krb5_etype_info_entry ** entry = 0; krb5_key_data *client_key; krb5_error_code retval; krb5_data * scratch; krb5_enctype db_etype; int i = 0; int start = 0; int seen_des = 0; entry = malloc((client->n_key_data * 2 + 1) * sizeof(krb5_etype_info_entry *)); if (entry == NULL) return ENOMEM; entry[0] = NULL; while (1) { retval = krb5_dbe_search_enctype(context, client, &start, -1, -1, 0, &client_key); if (retval == KRB5_KDB_NO_MATCHING_KEY) break; if (retval) goto cleanup; db_etype = client_key->key_data_type[0]; if (db_etype == ENCTYPE_DES_CBC_MD4) db_etype = ENCTYPE_DES_CBC_MD5; if (request_contains_enctype(context, request, db_etype)) { assert(etype_info2 || !enctype_requires_etype_info_2(db_etype)); if ((retval = _make_etype_info_entry(context, request, client_key, db_etype, &entry[i], etype_info2)) != 0) { goto cleanup; } entry[i+1] = 0; i++; } /* * If there is a des key in the kdb, try the "similar" enctypes, * avoid duplicate entries. */ if (!seen_des) { switch (db_etype) { case ENCTYPE_DES_CBC_MD5: db_etype = ENCTYPE_DES_CBC_CRC; break; case ENCTYPE_DES_CBC_CRC: db_etype = ENCTYPE_DES_CBC_MD5; break; default: continue; } if (request_contains_enctype(context, request, db_etype)) { if ((retval = _make_etype_info_entry(context, request, client_key, db_etype, &entry[i], etype_info2)) != 0) { goto cleanup; } entry[i+1] = 0; i++; } seen_des++; } } if (etype_info2) retval = encode_krb5_etype_info2((krb5_etype_info_entry * const*) entry, &scratch); else retval = encode_krb5_etype_info((krb5_etype_info_entry * const*) entry, &scratch); if (retval) goto cleanup; pa_data->contents = (unsigned char *)scratch->data; pa_data->length = scratch->length; free(scratch); retval = 0; cleanup: if (entry) krb5_free_etype_info(context, entry); return retval; } static krb5_error_code get_etype_info(krb5_context context, krb5_kdc_req *request, krb5_db_entry *client, krb5_db_entry *server, preauth_get_entry_data_proc etype_get_entry_data, void *pa_system_context, krb5_pa_data *pa_data) { int i; for (i=0; i < request->nktypes; i++) { if (enctype_requires_etype_info_2(request->ktype[i])) return KRB5KDC_ERR_PADATA_TYPE_NOSUPP ;;;; /*Caller will * skip this * type*/ } return etype_info_helper(context, request, client, server, pa_data, 0); } static krb5_error_code get_etype_info2(krb5_context context, krb5_kdc_req *request, krb5_db_entry *client, krb5_db_entry *server, preauth_get_entry_data_proc etype_get_entry_data, void *pa_system_context, krb5_pa_data *pa_data) { return etype_info_helper( context, request, client, server, pa_data, 1); } static krb5_error_code etype_info_as_rep_helper(krb5_context context, krb5_pa_data * padata, krb5_db_entry *client, krb5_kdc_req *request, krb5_kdc_rep *reply, krb5_key_data *client_key, krb5_keyblock *encrypting_key, krb5_pa_data **send_pa, int etype_info2) { int i; krb5_error_code retval; krb5_pa_data *tmp_padata; krb5_etype_info_entry **entry = NULL; krb5_data *scratch = NULL; /* * Skip PA-ETYPE-INFO completely if AS-REQ lists any "newer" * enctypes. */ if (!etype_info2) { for (i = 0; i < request->nktypes; i++) { if (enctype_requires_etype_info_2(request->ktype[i])) { *send_pa = NULL; return 0; } } } tmp_padata = malloc( sizeof(krb5_pa_data)); if (tmp_padata == NULL) return ENOMEM; if (etype_info2) tmp_padata->pa_type = KRB5_PADATA_ETYPE_INFO2; else tmp_padata->pa_type = KRB5_PADATA_ETYPE_INFO; entry = malloc(2 * sizeof(krb5_etype_info_entry *)); if (entry == NULL) { retval = ENOMEM; goto cleanup; } entry[0] = NULL; entry[1] = NULL; retval = _make_etype_info_entry(context, request, client_key, encrypting_key->enctype, entry, etype_info2); if (retval) goto cleanup; if (etype_info2) retval = encode_krb5_etype_info2((krb5_etype_info_entry * const*) entry, &scratch); else retval = encode_krb5_etype_info((krb5_etype_info_entry * const*) entry, &scratch); if (retval) goto cleanup; tmp_padata->contents = (uchar_t *)scratch->data; tmp_padata->length = scratch->length; *send_pa = tmp_padata; /* For cleanup - we no longer own the contents of the krb5_data * only to pointer to the krb5_data */ scratch->data = 0; cleanup: if (entry) krb5_free_etype_info(context, entry); if (retval) { if (tmp_padata) free(tmp_padata); } if (scratch) krb5_free_data(context, scratch); return retval; } static krb5_error_code return_etype_info2(krb5_context context, krb5_pa_data * padata, krb5_db_entry *client, krb5_data *req_pkt, krb5_kdc_req *request, krb5_kdc_rep *reply, krb5_key_data *client_key, krb5_keyblock *encrypting_key, krb5_pa_data **send_pa, preauth_get_entry_data_proc etype_get_entry_data, void *pa_system_context, void **pa_request_context) { return etype_info_as_rep_helper(context, padata, client, request, reply, client_key, encrypting_key, send_pa, 1); } static krb5_error_code return_etype_info(krb5_context context, krb5_pa_data * padata, krb5_db_entry *client, krb5_data *req_pkt, krb5_kdc_req *request, krb5_kdc_rep *reply, krb5_key_data *client_key, krb5_keyblock *encrypting_key, krb5_pa_data **send_pa, preauth_get_entry_data_proc etypeget_entry_data, void *pa_system_context, void **pa_request_context) { return etype_info_as_rep_helper(context, padata, client, request, reply, client_key, encrypting_key, send_pa, 0); } static krb5_error_code return_pw_salt(krb5_context context, krb5_pa_data *in_padata, krb5_db_entry *client, krb5_data *req_pkt, krb5_kdc_req *request, krb5_kdc_rep *reply, krb5_key_data *client_key, krb5_keyblock *encrypting_key, krb5_pa_data **send_pa, preauth_get_entry_data_proc etype_get_entry_data, void *pa_system_context, void **pa_request_context) { krb5_error_code retval; krb5_pa_data * padata; krb5_data * scratch; krb5_data salt_data; int i; for (i = 0; i < request->nktypes; i++) { if (enctype_requires_etype_info_2(request->ktype[i])) return 0; } if (client_key->key_data_ver == 1 || client_key->key_data_type[1] == KRB5_KDB_SALTTYPE_NORMAL) return 0; if ((padata = malloc(sizeof(krb5_pa_data))) == NULL) return ENOMEM; padata->magic = KV5M_PA_DATA; padata->pa_type = KRB5_PADATA_PW_SALT; switch (client_key->key_data_type[1]) { case KRB5_KDB_SALTTYPE_V4: /* send an empty (V4) salt */ padata->contents = 0; padata->length = 0; break; case KRB5_KDB_SALTTYPE_NOREALM: if ((retval = krb5_principal2salt_norealm(kdc_context, request->client, &salt_data))) goto cleanup; padata->contents = (krb5_octet *)salt_data.data; padata->length = salt_data.length; break; case KRB5_KDB_SALTTYPE_AFS3: /* send an AFS style realm-based salt */ /* for now, just pass the realm back and let the client do the work. In the future, add a kdc configuration variable that specifies the old cell name. */ padata->pa_type = KRB5_PADATA_AFS3_SALT; /* it would be just like ONLYREALM, but we need to pass the 0 */ scratch = krb5_princ_realm(kdc_context, request->client); if ((padata->contents = malloc(scratch->length+1)) == NULL) { retval = ENOMEM; goto cleanup; } memcpy(padata->contents, scratch->data, scratch->length); padata->length = scratch->length+1; padata->contents[scratch->length] = 0; break; case KRB5_KDB_SALTTYPE_ONLYREALM: scratch = krb5_princ_realm(kdc_context, request->client); if ((padata->contents = malloc(scratch->length)) == NULL) { retval = ENOMEM; goto cleanup; } memcpy(padata->contents, scratch->data, scratch->length); padata->length = scratch->length; break; case KRB5_KDB_SALTTYPE_SPECIAL: if ((padata->contents = malloc(client_key->key_data_length[1])) == NULL) { retval = ENOMEM; goto cleanup; } memcpy(padata->contents, client_key->key_data_contents[1], client_key->key_data_length[1]); padata->length = client_key->key_data_length[1]; break; default: free(padata); return 0; } *send_pa = padata; return 0; cleanup: free(padata); return retval; } static krb5_error_code return_sam_data(krb5_context context, krb5_pa_data *in_padata, krb5_db_entry *client, krb5_data *req_pkt, krb5_kdc_req *request, krb5_kdc_rep *reply, krb5_key_data *client_key, krb5_keyblock *encrypting_key, krb5_pa_data **send_pa, preauth_get_entry_data_proc sam_get_entry_data, void *pa_system_context, void **pa_request_context) { krb5_error_code retval; krb5_data scratch; int i; krb5_sam_response *sr = 0; krb5_predicted_sam_response *psr = 0; if (in_padata == 0) return 0; /* * We start by doing the same thing verify_sam_response() does: * extract the psr from the padata (which is an sr). Nothing * here should generate errors! We've already successfully done * all this once. */ scratch.data = (char *) in_padata->contents; /* SUNWresync121 XXX */ scratch.length = in_padata->length; if ((retval = decode_krb5_sam_response(&scratch, &sr))) { com_err("krb5kdc", retval, gettext("return_sam_data(): decode_krb5_sam_response failed")); goto cleanup; } { krb5_enc_data tmpdata; tmpdata.enctype = ENCTYPE_UNKNOWN; tmpdata.ciphertext = sr->sam_track_id; scratch.length = tmpdata.ciphertext.length; if ((scratch.data = (char *) malloc(scratch.length)) == NULL) { retval = ENOMEM; goto cleanup; } if ((retval = krb5_c_decrypt(context, &psr_key, /* XXX */ 0, 0, &tmpdata, &scratch))) { com_err("krb5kdc", retval, gettext("return_sam_data(): decrypt track_id failed")); free(scratch.data); goto cleanup; } } if ((retval = decode_krb5_predicted_sam_response(&scratch, &psr))) { com_err("krb5kdc", retval, gettext( "return_sam_data(): decode_krb5_predicted_sam_response failed")); free(scratch.data); goto cleanup; } /* We could use sr->sam_flags, but it may be absent or altered. */ if (psr->sam_flags & KRB5_SAM_MUST_PK_ENCRYPT_SAD) { com_err("krb5kdc", retval = KRB5KDC_ERR_PREAUTH_FAILED, gettext("Unsupported SAM flag must-pk-encrypt-sad")); goto cleanup; } if (psr->sam_flags & KRB5_SAM_SEND_ENCRYPTED_SAD) { /* No key munging */ goto cleanup; } if (psr->sam_flags & KRB5_SAM_USE_SAD_AS_KEY) { /* Use sam_key instead of client key */ krb5_free_keyblock_contents(context, encrypting_key); krb5_copy_keyblock_contents(context, &psr->sam_key, encrypting_key); /* XXX Attach a useful pa_data */ goto cleanup; } /* Otherwise (no flags set), we XOR the keys */ /* XXX The passwords-04 draft is underspecified here wrt different key types. We will do what I hope to get into the -05 draft. */ { krb5_octet *p = encrypting_key->contents; krb5_octet *q = psr->sam_key.contents; int length = ((encrypting_key->length < psr->sam_key.length) ? encrypting_key->length : psr->sam_key.length); for (i = 0; i < length; i++) p[i] ^= q[i]; } /* Post-mixing key correction */ switch (encrypting_key->enctype) { case ENCTYPE_DES_CBC_CRC: case ENCTYPE_DES_CBC_MD4: case ENCTYPE_DES_CBC_MD5: case ENCTYPE_DES_CBC_RAW: mit_des_fixup_key_parity(encrypting_key->contents); if (mit_des_is_weak_key(encrypting_key->contents)) ((krb5_octet *) encrypting_key->contents)[7] ^= 0xf0; break; /* XXX case ENCTYPE_DES3_CBC_MD5: listed in 1510bis-04 draft */ case ENCTYPE_DES3_CBC_SHA: /* XXX deprecated? */ case ENCTYPE_DES3_CBC_RAW: case ENCTYPE_DES3_CBC_SHA1: for (i = 0; i < 3; i++) { mit_des_fixup_key_parity(encrypting_key->contents + i * 8); if (mit_des_is_weak_key(encrypting_key->contents + i * 8)) ((krb5_octet *) encrypting_key->contents)[7 + i * 8] ^= 0xf0; } break; default: com_err("krb5kdc", retval = KRB5KDC_ERR_PREAUTH_FAILED, gettext("Unimplemented keytype for SAM key mixing")); goto cleanup; } /* XXX Attach a useful pa_data */ cleanup: if (sr) krb5_free_sam_response(context, sr); if (psr) krb5_free_predicted_sam_response(context, psr); return retval; } static struct { char* name; int sam_type; } *sam_ptr, sam_inst_map[] = { #if 0 /* SUNWresync121 - unsupported hardware and kdc.log annoyance */ { "SNK4", PA_SAM_TYPE_DIGI_PATH, }, { "SECURID", PA_SAM_TYPE_SECURID, }, { "GRAIL", PA_SAM_TYPE_GRAIL, }, #endif { 0, 0 }, }; static krb5_error_code get_sam_edata(krb5_context context, krb5_kdc_req *request, krb5_db_entry *client, krb5_db_entry *server, preauth_get_entry_data_proc sam_get_entry_data, void *pa_system_context, krb5_pa_data *pa_data) { krb5_error_code retval; krb5_sam_challenge sc; krb5_predicted_sam_response psr; krb5_data * scratch; krb5_keyblock encrypting_key; char response[9]; char inputblock[8]; krb5_data predict_response; (void) memset(&encrypting_key, 0, sizeof(krb5_keyblock)); (void) memset(&sc, 0, sizeof(sc)); (void) memset(&psr, 0, sizeof(psr)); /* Given the client name we can figure out what type of preauth they need. The spec is currently for querying the database for names that match the types of preauth used. Later we should make this mapping show up in kdc.conf. In the meantime, we hardcode the following: /SNK4 -- Digital Pathways SNK/4 preauth. /GRAIL -- experimental preauth The first one found is used. See sam_inst_map above. For SNK4 in particular, the key in the database is the key for the device; kadmin needs a special interface for it. */ { int npr = 1; krb5_boolean more; krb5_db_entry assoc; krb5_key_data *assoc_key; krb5_principal newp; int probeslot; sc.sam_type = 0; retval = krb5_copy_principal(kdc_context, request->client, &newp); if (retval) { com_err(gettext("krb5kdc"), retval, gettext("copying client name for preauth probe")); return retval; } probeslot = krb5_princ_size(context, newp)++; krb5_princ_name(kdc_context, newp) = realloc(krb5_princ_name(kdc_context, newp), krb5_princ_size(context, newp) * sizeof(krb5_data)); for(sam_ptr = sam_inst_map; sam_ptr->name; sam_ptr++) { krb5_princ_component(kdc_context,newp,probeslot)->data = sam_ptr->name; krb5_princ_component(kdc_context,newp,probeslot)->length = strlen(sam_ptr->name); npr = 1; retval = krb5_db_get_principal(kdc_context, newp, &assoc, &npr, (uint *)&more); if(!retval && npr) { sc.sam_type = sam_ptr->sam_type; break; } } krb5_princ_component(kdc_context,newp,probeslot)->data = 0; krb5_princ_component(kdc_context,newp,probeslot)->length = 0; krb5_princ_size(context, newp)--; krb5_free_principal(kdc_context, newp); /* if sc.sam_type is set, it worked */ if (sc.sam_type) { /* so use assoc to get the key out! */ { /* here's what do_tgs_req does */ retval = krb5_dbe_find_enctype(kdc_context, &assoc, ENCTYPE_DES_CBC_RAW, KRB5_KDB_SALTTYPE_NORMAL, 0, /* Get highest kvno */ &assoc_key); if (retval) { char *sname; krb5_unparse_name(kdc_context, request->client, &sname); com_err(gettext("krb5kdc"), retval, gettext("snk4 finding the enctype and key <%s>"), sname); free(sname); return retval; } /* convert server.key into a real key */ retval = krb5_dbekd_decrypt_key_data(kdc_context, &master_keyblock, assoc_key, &encrypting_key, NULL); if (retval) { com_err(gettext("krb5kdc"), retval, gettext("snk4 pulling out key entry")); return retval; } /* now we can use encrypting_key... */ } } else { /* SAM is not an option - so don't return as hint */ return KRB5_PREAUTH_BAD_TYPE; } } sc.magic = KV5M_SAM_CHALLENGE; psr.sam_flags = sc.sam_flags = KRB5_SAM_USE_SAD_AS_KEY; /* Replay prevention */ if ((retval = krb5_copy_principal(context, request->client, &psr.client))) return retval; #ifdef USE_RCACHE if ((retval = krb5_us_timeofday(context, &psr.stime, &psr.susec))) return retval; #endif /* USE_RCACHE */ switch (sc.sam_type) { case PA_SAM_TYPE_GRAIL: sc.sam_type_name.data = "Experimental System"; sc.sam_type_name.length = strlen(sc.sam_type_name.data); sc.sam_challenge_label.data = "experimental challenge label"; sc.sam_challenge_label.length = strlen(sc.sam_challenge_label.data); sc.sam_challenge.data = "12345"; sc.sam_challenge.length = strlen(sc.sam_challenge.data); #if 0 /* Enable this to test "normal" (no flags set) mode. */ psr.sam_flags = sc.sam_flags = 0; #endif psr.magic = KV5M_PREDICTED_SAM_RESPONSE; /* string2key on sc.sam_challenge goes in here */ /* eblock is just to set the enctype */ { const krb5_enctype type = ENCTYPE_DES_CBC_MD5; if ((retval = krb5_c_string_to_key(context, type, &sc.sam_challenge, 0 /* salt */, &psr.sam_key))) goto cleanup; if ((retval = encode_krb5_predicted_sam_response(&psr, &scratch))) goto cleanup; { size_t enclen; krb5_enc_data tmpdata; if ((retval = krb5_c_encrypt_length(context, psr_key.enctype, scratch->length, &enclen))) goto cleanup; if ((tmpdata.ciphertext.data = (char *) malloc(enclen)) == NULL) { retval = ENOMEM; goto cleanup; } tmpdata.ciphertext.length = enclen; if ((retval = krb5_c_encrypt(context, &psr_key, /* XXX */ 0, 0, scratch, &tmpdata))) goto cleanup; sc.sam_track_id = tmpdata.ciphertext; } } sc.sam_response_prompt.data = "response prompt"; sc.sam_response_prompt.length = strlen(sc.sam_response_prompt.data); sc.sam_pk_for_sad.length = 0; sc.sam_nonce = 0; /* Generate checksum */ /*krb5_checksum_size(context, ctype)*/ /*krb5_calculate_checksum(context,ctype,in,in_length,seed, seed_length,outcksum) */ /*krb5_verify_checksum(context,ctype,cksum,in,in_length,seed, seed_length) */ #if 0 /* XXX a) glue appears broken; b) this gives up the SAD */ sc.sam_cksum.contents = (krb5_octet *) malloc(krb5_checksum_size(context, CKSUMTYPE_RSA_MD5_DES)); if (sc.sam_cksum.contents == NULL) return(ENOMEM); retval = krb5_calculate_checksum(context, CKSUMTYPE_RSA_MD5_DES, sc.sam_challenge.data, sc.sam_challenge.length, psr.sam_key.contents, /* key */ psr.sam_key.length, /* key length */ &sc.sam_cksum); if (retval) { free(sc.sam_cksum.contents); return(retval); } #endif /* 0 */ retval = encode_krb5_sam_challenge(&sc, &scratch); if (retval) goto cleanup; pa_data->magic = KV5M_PA_DATA; pa_data->pa_type = KRB5_PADATA_SAM_CHALLENGE; pa_data->contents = (unsigned char *) scratch->data; pa_data->length = scratch->length; retval = 0; break; case PA_SAM_TYPE_DIGI_PATH: sc.sam_type_name.data = "Digital Pathways"; sc.sam_type_name.length = strlen(sc.sam_type_name.data); #if 1 sc.sam_challenge_label.data = "Enter the following on your keypad"; sc.sam_challenge_label.length = strlen(sc.sam_challenge_label.data); #endif /* generate digit string, take it mod 1000000 (six digits.) */ { int j; krb5_keyblock session_key; char outputblock[8]; int i; (void) memset(&session_key, 0, sizeof(krb5_keyblock)); (void) memset(inputblock, 0, 8); retval = krb5_c_make_random_key(kdc_context, ENCTYPE_DES_CBC_CRC, &session_key); if (retval) { /* random key failed */ com_err(gettext("krb5kdc"), retval, gettext("generating random challenge for preauth")); return retval; } /* now session_key has a key which we can pick bits out of */ /* we need six decimal digits. Grab 6 bytes, div 2, mod 10 each. */ if (session_key.length != 8) { retval = KRB5KDC_ERR_ETYPE_NOSUPP, com_err(gettext("krb5kdc"), retval = KRB5KDC_ERR_ETYPE_NOSUPP, gettext("keytype didn't match code expectations")); return retval; } for(i = 0; i<6; i++) { inputblock[i] = '0' + ((session_key.contents[i]/2) % 10); } if (session_key.contents) krb5_free_keyblock_contents(kdc_context, &session_key); /* retval = krb5_finish_key(kdc_context, &eblock); */ /* now we have inputblock containing the 8 byte input to DES... */ sc.sam_challenge.data = inputblock; sc.sam_challenge.length = 6; encrypting_key.enctype = ENCTYPE_DES_CBC_RAW; if (retval) { com_err(gettext("krb5kdc"), retval, gettext("snk4 processing key")); } { krb5_data plain; krb5_enc_data cipher; plain.length = 8; plain.data = inputblock; /* XXX I know this is enough because of the fixed raw enctype. if it's not, the underlying code will return a reasonable error, which should never happen */ cipher.ciphertext.length = 8; cipher.ciphertext.data = outputblock; if ((retval = krb5_c_encrypt(kdc_context, &encrypting_key, /* XXX */ 0, 0, &plain, &cipher))) { com_err(gettext("krb5kdc"), retval, gettext("snk4 response generation failed")); return retval; } } /* now output block is the raw bits of the response; convert it to display form */ for (j=0; j<4; j++) { char n[2]; int k; n[0] = outputblock[j] & 0xf; n[1] = (outputblock[j]>>4) & 0xf; for (k=0; k<2; k++) { if(n[k] > 9) n[k] = ((n[k]-1)>>2); /* This is equivalent to: if(n[k]>=0xa && n[k]<=0xc) n[k] = 2; if(n[k]>=0xd && n[k]<=0xf) n[k] = 3; */ } /* for v4, we keygen: *(j+(char*)&key1) = (n[1]<<4) | n[0]; */ /* for v5, we just generate a string */ response[2*j+0] = '0' + n[1]; response[2*j+1] = '0' + n[0]; /* and now, response has what we work with. */ } response[8] = 0; predict_response.data = response; predict_response.length = 8; #if 0 /* for debugging, hack the output too! */ sc.sam_challenge_label.data = response; sc.sam_challenge_label.length = strlen(sc.sam_challenge_label.data); #endif } psr.magic = KV5M_PREDICTED_SAM_RESPONSE; /* string2key on sc.sam_challenge goes in here */ /* eblock is just to set the enctype */ { retval = krb5_c_string_to_key(context, ENCTYPE_DES_CBC_MD5, &predict_response, 0 /* salt */, &psr.sam_key); if (retval) goto cleanup; retval = encode_krb5_predicted_sam_response(&psr, &scratch); if (retval) goto cleanup; { size_t enclen; krb5_enc_data tmpdata; if ((retval = krb5_c_encrypt_length(context, psr_key.enctype, scratch->length, &enclen))) goto cleanup; if ((tmpdata.ciphertext.data = (char *) malloc(enclen)) == NULL) { retval = ENOMEM; goto cleanup; } tmpdata.ciphertext.length = enclen; if ((retval = krb5_c_encrypt(context, &psr_key, /* XXX */ 0, 0, scratch, &tmpdata))) goto cleanup; sc.sam_track_id = tmpdata.ciphertext; } if (retval) goto cleanup; } sc.sam_response_prompt.data = "Enter the displayed response"; sc.sam_response_prompt.length = strlen(sc.sam_response_prompt.data); sc.sam_pk_for_sad.length = 0; sc.sam_nonce = 0; /* Generate checksum */ /*krb5_checksum_size(context, ctype)*/ /*krb5_calculate_checksum(context,ctype,in,in_length,seed, seed_length,outcksum) */ /*krb5_verify_checksum(context,ctype,cksum,in,in_length,seed, seed_length) */ #if 0 /* XXX a) glue appears broken; b) this gives up the SAD */ sc.sam_cksum.contents = (krb5_octet *) malloc(krb5_checksum_size(context, CKSUMTYPE_RSA_MD5_DES)); if (sc.sam_cksum.contents == NULL) return(ENOMEM); retval = krb5_calculate_checksum(context, CKSUMTYPE_RSA_MD5_DES, sc.sam_challenge.data, sc.sam_challenge.length, psr.sam_key.contents, /* key */ psr.sam_key.length, /* key length */ &sc.sam_cksum); if (retval) { free(sc.sam_cksum.contents); return(retval); } #endif /* 0 */ retval = encode_krb5_sam_challenge(&sc, &scratch); if (retval) goto cleanup; pa_data->magic = KV5M_PA_DATA; pa_data->pa_type = KRB5_PADATA_SAM_CHALLENGE; pa_data->contents = (unsigned char *) scratch->data; pa_data->length = scratch->length; retval = 0; break; } cleanup: krb5_free_keyblock_contents(context, &encrypting_key); return retval; } static krb5_error_code verify_sam_response(krb5_context context, krb5_db_entry *client, krb5_data *req_pkt, krb5_kdc_req *request, krb5_enc_tkt_part *enc_tkt_reply, krb5_pa_data *pa, preauth_get_entry_data_proc sam_get_entry_data, void *pa_system_context, void **pa_request_context, krb5_data **e_data, krb5_authdata ***authz_data) { krb5_error_code retval; krb5_data scratch; krb5_sam_response *sr = 0; krb5_predicted_sam_response *psr = 0; krb5_enc_sam_response_enc *esre = 0; krb5_timestamp timenow; char *princ_req = 0, *princ_psr = 0; scratch.data = (char *) pa->contents; scratch.length = pa->length; if ((retval = decode_krb5_sam_response(&scratch, &sr))) { scratch.data = 0; com_err("krb5kdc", retval, gettext("decode_krb5_sam_response failed")); goto cleanup; } /* XXX We can only handle the challenge/response model of SAM. See passwords-04, par 4.1, 4.2 */ { krb5_enc_data tmpdata; tmpdata.enctype = ENCTYPE_UNKNOWN; tmpdata.ciphertext = sr->sam_track_id; scratch.length = tmpdata.ciphertext.length; if ((scratch.data = (char *) malloc(scratch.length)) == NULL) { retval = ENOMEM; goto cleanup; } if ((retval = krb5_c_decrypt(context, &psr_key, /* XXX */ 0, 0, &tmpdata, &scratch))) { com_err(gettext("krb5kdc"), retval, gettext("decrypt track_id failed")); goto cleanup; } } if ((retval = decode_krb5_predicted_sam_response(&scratch, &psr))) { com_err(gettext("krb5kdc"), retval, gettext("decode_krb5_predicted_sam_response failed -- replay attack?")); goto cleanup; } /* Replay detection */ if ((retval = krb5_unparse_name(context, request->client, &princ_req))) goto cleanup; if ((retval = krb5_unparse_name(context, psr->client, &princ_psr))) goto cleanup; if (strcmp(princ_req, princ_psr) != 0) { com_err("krb5kdc", retval = KRB5KDC_ERR_PREAUTH_FAILED, gettext("Principal mismatch in SAM psr! -- replay attack?")); goto cleanup; } if ((retval = krb5_timeofday(context, &timenow))) goto cleanup; #ifdef USE_RCACHE { krb5_donot_replay rep; extern krb5_deltat rc_lifetime; /* * Verify this response came back in a timely manner. * We do this b/c otherwise very old (expunged from the rcache) * psr's would be able to be replayed. */ if (timenow - psr->stime > rc_lifetime) { com_err("krb5kdc", retval = KRB5KDC_ERR_PREAUTH_FAILED, gettext("SAM psr came back too late! -- replay attack?")); goto cleanup; } /* Now check the replay cache. */ rep.client = princ_psr; rep.server = "SAM/rc"; /* Should not match any principal name. */ rep.ctime = psr->stime; rep.cusec = psr->susec; retval = krb5_rc_store(kdc_context, kdc_rcache, &rep); if (retval) { com_err("krb5kdc", retval, gettext("SAM psr replay attack!")); goto cleanup; } } #endif /* USE_RCACHE */ { free(scratch.data); scratch.length = sr->sam_enc_nonce_or_ts.ciphertext.length; if ((scratch.data = (char *) malloc(scratch.length)) == NULL) { retval = ENOMEM; goto cleanup; } if ((retval = krb5_c_decrypt(context, &psr->sam_key, /* XXX */ 0, 0, &sr->sam_enc_nonce_or_ts, &scratch))) { com_err("krb5kdc", retval, gettext("decrypt nonce_or_ts failed")); goto cleanup; } } if ((retval = decode_krb5_enc_sam_response_enc(&scratch, &esre))) { com_err("krb5kdc", retval, gettext("decode_krb5_enc_sam_response_enc failed")); goto cleanup; } if (esre->sam_timestamp != sr->sam_patimestamp) { retval = KRB5KDC_ERR_PREAUTH_FAILED; goto cleanup; } if (labs(timenow - sr->sam_patimestamp) > context->clockskew) { retval = KRB5KRB_AP_ERR_SKEW; goto cleanup; } setflag(enc_tkt_reply->flags, TKT_FLG_HW_AUTH); cleanup: if (retval) com_err(gettext("krb5kdc"), retval, gettext("sam verify failure")); if (scratch.data) free(scratch.data); if (sr) free(sr); if (psr) free(psr); if (esre) free(esre); if (princ_psr) free(princ_psr); if (princ_req) free(princ_req); return retval; } /* * kdc/kdc_util.c * * Copyright 1990,1991 by the Massachusetts Institute of Technology. * All Rights Reserved. * * Export of this software from the United States of America may * require a specific license from the United States Government. * It is the responsibility of any person or organization contemplating * export to obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of M.I.T. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. Furthermore if you modify this software you must label * your software as modified software and not distribute it in such a * fashion that it might be confused with the original M.I.T. software. * M.I.T. makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. * * * Utility functions for the KDC implementation. */ #include "k5-int.h" #include "kdc_util.h" #include "extern.h" #include #include #include #include "adm.h" #include "adm_proto.h" #include #ifdef USE_RCACHE static char *kdc_current_rcname = (char *) NULL; krb5_deltat rc_lifetime; /* See kdc_initialize_rcache() */ #endif #ifdef USE_RCACHE /* * initialize the replay cache. */ krb5_error_code kdc_initialize_rcache(krb5_context kcontext, char *rcache_name) { krb5_error_code retval; char *rcname; char *sname; rcname = (rcache_name) ? rcache_name : kdc_current_rcname; /* rc_lifetime used elsewhere to verify we're not */ /* replaying really old data */ rc_lifetime = kcontext->clockskew; if (!rcname) rcname = KDCRCACHE; if (!(retval = krb5_rc_resolve_full(kcontext, &kdc_rcache, rcname))) { /* Recover or initialize the replay cache */ if (!(retval = krb5_rc_recover(kcontext, kdc_rcache)) || !(retval = krb5_rc_initialize(kcontext, kdc_rcache, kcontext->clockskew)) ) { /* Expunge the replay cache */ if (!(retval = krb5_rc_expunge(kcontext, kdc_rcache))) { sname = kdc_current_rcname; kdc_current_rcname = strdup(rcname); if (sname) free(sname); } } if (retval) krb5_rc_close(kcontext, kdc_rcache); } return(retval); } #endif /* * concatenate first two authdata arrays, returning an allocated replacement. * The replacement should be freed with krb5_free_authdata(). */ krb5_error_code concat_authorization_data(krb5_authdata **first, krb5_authdata **second, krb5_authdata ***output) { register int i, j; register krb5_authdata **ptr, **retdata; /* count up the entries */ i = 0; if (first) for (ptr = first; *ptr; ptr++) i++; if (second) for (ptr = second; *ptr; ptr++) i++; retdata = (krb5_authdata **)malloc((i+1)*sizeof(*retdata)); if (!retdata) return ENOMEM; retdata[i] = 0; /* null-terminated array */ for (i = 0, j = 0, ptr = first; j < 2 ; ptr = second, j++) while (ptr && *ptr) { /* now walk & copy */ retdata[i] = (krb5_authdata *)malloc(sizeof(*retdata[i])); if (!retdata[i]) { krb5_free_authdata(kdc_context, retdata); return ENOMEM; } *retdata[i] = **ptr; if (!(retdata[i]->contents = (krb5_octet *)malloc(retdata[i]->length))) { free((char *)retdata[i]); retdata[i] = 0; krb5_free_authdata(kdc_context, retdata); return ENOMEM; } memcpy((char *) retdata[i]->contents, (char *)(*ptr)->contents, retdata[i]->length); ptr++; i++; } *output = retdata; return 0; } krb5_boolean realm_compare(krb5_principal princ1, krb5_principal princ2) { krb5_data *realm1 = krb5_princ_realm(kdc_context, princ1); krb5_data *realm2 = krb5_princ_realm(kdc_context, princ2); return((realm1->length == realm2->length) && !memcmp(realm1->data, realm2->data, realm1->length)); } /* * Returns TRUE if the kerberos principal is the name of a Kerberos ticket * service. */ krb5_boolean krb5_is_tgs_principal(krb5_principal principal) { if ((krb5_princ_size(kdc_context, principal) > 0) && (krb5_princ_component(kdc_context, principal, 0)->length == KRB5_TGS_NAME_SIZE) && (!memcmp(krb5_princ_component(kdc_context, principal, 0)->data, KRB5_TGS_NAME, KRB5_TGS_NAME_SIZE))) return TRUE; return FALSE; } /* * given authentication data (provides seed for checksum), verify checksum * for source data. */ static krb5_error_code comp_cksum(krb5_context kcontext, krb5_data *source, krb5_ticket *ticket, krb5_checksum *his_cksum) { krb5_error_code retval; krb5_boolean valid; if (!krb5_c_valid_cksumtype(his_cksum->checksum_type)) return KRB5KDC_ERR_SUMTYPE_NOSUPP; /* must be collision proof */ if (!krb5_c_is_coll_proof_cksum(his_cksum->checksum_type)) return KRB5KRB_AP_ERR_INAPP_CKSUM; /* verify checksum */ if ((retval = krb5_c_verify_checksum(kcontext, ticket->enc_part2->session, KRB5_KEYUSAGE_TGS_REQ_AUTH_CKSUM, source, his_cksum, &valid))) return(retval); if (!valid) return(KRB5KRB_AP_ERR_BAD_INTEGRITY); return(0); } krb5_error_code kdc_process_tgs_req(krb5_kdc_req *request, const krb5_fulladdr *from, krb5_data *pkt, krb5_ticket **ticket, krb5_keyblock **subkey) { krb5_pa_data ** tmppa; krb5_ap_req * apreq; krb5_error_code retval; krb5_data scratch1; krb5_data * scratch = NULL; krb5_boolean foreign_server = FALSE; krb5_auth_context auth_context = NULL; krb5_authenticator * authenticator = NULL; krb5_checksum * his_cksum = NULL; /* krb5_keyblock * key = NULL;*/ /* krb5_kvno kvno = 0;*/ if (!request->padata) return KRB5KDC_ERR_PADATA_TYPE_NOSUPP; for (tmppa = request->padata; *tmppa; tmppa++) { if ((*tmppa)->pa_type == KRB5_PADATA_AP_REQ) break; } if (!*tmppa) /* cannot find any AP_REQ */ return KRB5KDC_ERR_PADATA_TYPE_NOSUPP; scratch1.length = (*tmppa)->length; scratch1.data = (char *)(*tmppa)->contents; if ((retval = decode_krb5_ap_req(&scratch1, &apreq))) return retval; if (isflagset(apreq->ap_options, AP_OPTS_USE_SESSION_KEY) || isflagset(apreq->ap_options, AP_OPTS_MUTUAL_REQUIRED)) { krb5_klog_syslog(LOG_INFO, "TGS_REQ: SESSION KEY or MUTUAL"); retval = KRB5KDC_ERR_POLICY; goto cleanup; } /* If the "server" principal in the ticket is not something in the local realm, then we must refuse to service the request if the client claims to be from the local realm. If we don't do this, then some other realm's nasty KDC can claim to be authenticating a client from our realm, and we'll give out tickets concurring with it! we set a flag here for checking below. */ if ((krb5_princ_realm(kdc_context, apreq->ticket->server)->length != krb5_princ_realm(kdc_context, tgs_server)->length) || memcmp(krb5_princ_realm(kdc_context, apreq->ticket->server)->data, krb5_princ_realm(kdc_context, tgs_server)->data, krb5_princ_realm(kdc_context, tgs_server)->length)) foreign_server = TRUE; if ((retval = krb5_auth_con_init(kdc_context, &auth_context))) goto cleanup; if ((retval = krb5_auth_con_setaddrs(kdc_context, auth_context, NULL, from->address)) ) goto cleanup_auth_context; #ifdef USE_RCACHE if ((retval = krb5_auth_con_setrcache(kdc_context, auth_context, kdc_rcache))) goto cleanup_auth_context; #endif /* if ((retval = kdc_get_server_key(apreq->ticket, &key, &kvno))) goto cleanup_auth_context; */ /* * XXX This is currently wrong but to fix it will require making a * new keytab for groveling over the kdb. */ /* retval = krb5_auth_con_setuseruserkey(kdc_context, auth_context, key); krb5_free_keyblock(kdc_context, key); if (retval) goto cleanup_auth_context; */ if ((retval = krb5_rd_req_decoded_anyflag(kdc_context, &auth_context, apreq, apreq->ticket->server, kdc_active_realm->realm_keytab, NULL, ticket))) { #ifdef USE_RCACHE /* * I'm not so sure that this is right, but it's better than nothing * at all. * * If we choke in the rd_req because of the replay cache, then attempt * to reinitialize the replay cache because somebody could have deleted * it from underneath us (e.g. a cron job) */ if ((retval == KRB5_RC_IO_IO) || (retval == KRB5_RC_IO_UNKNOWN)) { (void) krb5_rc_close(kdc_context, kdc_rcache); kdc_rcache = (krb5_rcache) NULL; if (!(retval = kdc_initialize_rcache(kdc_context, (char *) NULL))) { if ((retval = krb5_auth_con_setrcache(kdc_context, auth_context, kdc_rcache)) || (retval = krb5_rd_req_decoded_anyflag(kdc_context, &auth_context, apreq, apreq->ticket->server, kdc_active_realm->realm_keytab, NULL, ticket)) ) goto cleanup_auth_context; } } else goto cleanup_auth_context; #else goto cleanup_auth_context; #endif } /* "invalid flag" tickets can must be used to validate */ if (isflagset((*ticket)->enc_part2->flags, TKT_FLG_INVALID) && !isflagset(request->kdc_options, KDC_OPT_VALIDATE)) { retval = KRB5KRB_AP_ERR_TKT_INVALID; goto cleanup_auth_context; } if ((retval = krb5_auth_con_getrecvsubkey(kdc_context, auth_context, subkey))) goto cleanup_auth_context; if ((retval = krb5_auth_con_getauthenticator(kdc_context, auth_context, &authenticator))) goto cleanup_auth_context; /* Check for a checksum */ if (!(his_cksum = authenticator->checksum)) { retval = KRB5KRB_AP_ERR_INAPP_CKSUM; goto cleanup_authenticator; } /* make sure the client is of proper lineage (see above) */ if (foreign_server) { krb5_data *tkt_realm = krb5_princ_realm(kdc_context, (*ticket)->enc_part2->client); krb5_data *tgs_realm = krb5_princ_realm(kdc_context, tgs_server); if (tkt_realm->length == tgs_realm->length && !memcmp(tkt_realm->data, tgs_realm->data, tgs_realm->length)) { /* someone in a foreign realm claiming to be local */ krb5_klog_syslog(LOG_INFO, "PROCESS_TGS: failed lineage check"); retval = KRB5KDC_ERR_POLICY; goto cleanup_authenticator; } } /* * Check application checksum vs. tgs request * * We try checksumming the req-body two different ways: first we * try reaching into the raw asn.1 stream (if available), and * checksum that directly; if that fails, then we try encoding * using our local asn.1 library. */ if (pkt && (fetch_asn1_field((unsigned char *) pkt->data, 1, 4, &scratch1) >= 0)) { if (comp_cksum(kdc_context, &scratch1, *ticket, his_cksum)) { if (!(retval = encode_krb5_kdc_req_body(request, &scratch))) retval = comp_cksum(kdc_context, scratch, *ticket, his_cksum); krb5_free_data(kdc_context, scratch); } } cleanup_authenticator: krb5_free_authenticator(kdc_context, authenticator); cleanup_auth_context: /* We do not want the free of the auth_context to close the rcache */ #ifdef USE_RCACHE (void) krb5_auth_con_setrcache(kdc_context, auth_context, 0); #endif krb5_auth_con_free(kdc_context, auth_context); cleanup: krb5_free_ap_req(kdc_context, apreq); return retval; } /* XXX This function should no longer be necessary. * The KDC should take the keytab associated with the realm and pass that to * the krb5_rd_req_decode(). --proven * * It's actually still used by do_tgs_req() for u2u auth, and not too * much else. -- tlyu */ krb5_error_code kdc_get_server_key(krb5_ticket *ticket, krb5_keyblock **key, krb5_kvno *kvno) { krb5_error_code retval; krb5_db_entry server; krb5_boolean more; int nprincs; krb5_key_data * server_key; nprincs = 1; if ((retval = krb5_db_get_principal(kdc_context, ticket->server, &server, &nprincs, &more))) { return(retval); } if (more) { krb5_db_free_principal(kdc_context, &server, nprincs); return(KRB5KDC_ERR_PRINCIPAL_NOT_UNIQUE); } else if (nprincs != 1) { char *sname; krb5_db_free_principal(kdc_context, &server, nprincs); if (!krb5_unparse_name(kdc_context, ticket->server, &sname)) { limit_string(sname); krb5_klog_syslog(LOG_ERR,"TGS_REQ: UNKNOWN SERVER: server='%s'", sname); free(sname); } return(KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN); } retval = krb5_dbe_find_enctype(kdc_context, &server, ticket->enc_part.enctype, -1, ticket->enc_part.kvno, &server_key); if (retval) goto errout; if (!server_key) { retval = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; goto errout; } *kvno = server_key->key_data_kvno; if ((*key = (krb5_keyblock *)malloc(sizeof **key))) { retval = krb5_dbekd_decrypt_key_data(kdc_context, &master_keyblock, server_key, *key, NULL); } else retval = ENOMEM; errout: krb5_db_free_principal(kdc_context, &server, nprincs); return retval; } /* This probably wants to be updated if you support last_req stuff */ static krb5_last_req_entry nolrentry = { KV5M_LAST_REQ_ENTRY, KRB5_LRQ_NONE, 0 }; static krb5_last_req_entry *nolrarray[] = { &nolrentry, 0 }; krb5_error_code fetch_last_req_info(krb5_db_entry *dbentry, krb5_last_req_entry ***lrentry) { *lrentry = nolrarray; return 0; } /* XXX! This is a temporary place-holder */ krb5_error_code check_hot_list(krb5_ticket *ticket) { return 0; } #define MAX_REALM_LN 500 /* * subrealm - determine if r2 is a subrealm of r1 * * SUBREALM takes two realms, r1 and r2, and * determines if r2 is a subrealm of r1. * r2 is a subrealm of r1 if (r1 is a prefix * of r2 AND r1 and r2 begin with a /) or if * (r1 is a suffix of r2 and neither r1 nor r2 * begin with a /). * * RETURNS: If r2 is a subrealm, and r1 is a prefix, the number * of characters in the suffix of r2 is returned as a * negative number. * * If r2 is a subrealm, and r1 is a suffix, the number * of characters in the prefix of r2 is returned as a * positive number. * * If r2 is not a subrealm, SUBREALM returns 0. */ static int subrealm(char *r1, char *r2) { size_t l1,l2; l1 = strlen(r1); l2 = strlen(r2); if(l2 <= l1) return(0); if((*r1 == '/') && (*r2 == '/') && (strncmp(r1,r2,l1) == 0)) return(l1-l2); if((*r1 != '/') && (*r2 != '/') && (strncmp(r1,r2+l2-l1,l1) == 0)) return(l2-l1); return(0); } /* * add_to_transited Adds the name of the realm which issued the * ticket granting ticket on which the new ticket to * be issued is based (note that this is the same as * the realm of the server listed in the ticket * granting ticket. * * ASSUMPTIONS: This procedure assumes that the transited field from * the existing ticket granting ticket already appears * in compressed form. It will add the new realm while * maintaining that form. As long as each successive * realm is added using this (or a similar) routine, the * transited field will be in compressed form. The * basis step is an empty transited field which is, by * its nature, in its most compressed form. * * ARGUMENTS: krb5_data *tgt_trans Transited field from TGT * krb5_data *new_trans The transited field for the new ticket * krb5_principal tgs Name of ticket granting server * This includes the realm of the KDC * that issued the ticket granting * ticket. This is the realm that is * to be added to the transited field. * krb5_principal client Name of the client * krb5_principal server The name of the requested server. * This may be the an intermediate * ticket granting server. * * The last two argument are needed since they are * implicitly part of the transited field of the new ticket * even though they are not explicitly listed. * * RETURNS: krb5_error_code - Success, or out of memory * * MODIFIES: new_trans: ->length will contain the length of the new * transited field. * * If ->data was not null when this procedure * is called, the memory referenced by ->data * will be deallocated. * * Memory will be allocated for the new transited field * ->data will be updated to point to the newly * allocated memory. * * BUGS: The space allocated for the new transited field is the * maximum that might be needed given the old transited field, * and the realm to be added. This length is calculated * assuming that no compression of the new realm is possible. * This has no adverse consequences other than the allocation * of more space than required. * * This procedure will not yet use the null subfield notation, * and it will get confused if it sees it. * * This procedure does not check for quoted commas in realm * names. */ static char * data2string (krb5_data *d) { char *s; s = malloc(d->length + 1); if (s) { memcpy(s, d->data, d->length); s[d->length] = 0; } return s; } krb5_error_code add_to_transited(krb5_data *tgt_trans, krb5_data *new_trans, krb5_principal tgs, krb5_principal client, krb5_principal server) { krb5_error_code retval; char *realm; char *trans; char *otrans, *otrans_ptr; /* The following are for stepping through the transited field */ char prev[MAX_REALM_LN]; char next[MAX_REALM_LN]; char current[MAX_REALM_LN]; char exp[MAX_REALM_LN]; /* Expanded current realm name */ int i; int clst, nlst; /* count of last character in current and next */ int pl, pl1; /* prefix length */ int added; /* TRUE = new realm has been added */ realm = data2string(krb5_princ_realm(kdc_context, tgs)); if (realm == NULL) return(ENOMEM); otrans = data2string(tgt_trans); if (otrans == NULL) { free(realm); return(ENOMEM); } /* Keep track of start so we can free */ otrans_ptr = otrans; /* +1 for null, +1 for extra comma which may be added between +1 for potential space when leading slash in realm */ if (!(trans = (char *) malloc(strlen(realm) + strlen(otrans) + 3))) { retval = ENOMEM; goto fail; } if (new_trans->data) free(new_trans->data); new_trans->data = trans; new_trans->length = 0; trans[0] = '\0'; /* For the purpose of appending, the realm preceding the first */ /* realm in the transited field is considered the null realm */ prev[0] = '\0'; /* read field into current */ for (i = 0; *otrans != '\0';) { if (*otrans == '\\') { if (*(++otrans) == '\0') break; else continue; } if (*otrans == ',') { otrans++; break; } current[i++] = *otrans++; if (i >= MAX_REALM_LN) { retval = KRB5KRB_AP_ERR_ILL_CR_TKT; goto fail; } } current[i] = '\0'; added = (krb5_princ_realm(kdc_context, client)->length == strlen(realm) && !strncmp(krb5_princ_realm(kdc_context, client)->data, realm, strlen(realm))) || (krb5_princ_realm(kdc_context, server)->length == strlen(realm) && !strncmp(krb5_princ_realm(kdc_context, server)->data, realm, strlen(realm))); while (current[0]) { /* figure out expanded form of current name */ clst = strlen(current) - 1; if (current[0] == ' ') { strncpy(exp, current+1, sizeof(exp) - 1); exp[sizeof(exp) - 1] = '\0'; } else if ((current[0] == '/') && (prev[0] == '/')) { strncpy(exp, prev, sizeof(exp) - 1); exp[sizeof(exp) - 1] = '\0'; if (strlen(exp) + strlen(current) + 1 >= MAX_REALM_LN) { retval = KRB5KRB_AP_ERR_ILL_CR_TKT; goto fail; } strncat(exp, current, sizeof(exp) - 1 - strlen(exp)); } else if (current[clst] == '.') { strncpy(exp, current, sizeof(exp) - 1); exp[sizeof(exp) - 1] = '\0'; if (strlen(exp) + strlen(prev) + 1 >= MAX_REALM_LN) { retval = KRB5KRB_AP_ERR_ILL_CR_TKT; goto fail; } strncat(exp, prev, sizeof(exp) - 1 - strlen(exp)); } else { strncpy(exp, current, sizeof(exp) - 1); exp[sizeof(exp) - 1] = '\0'; } /* read field into next */ for (i = 0; *otrans != '\0';) { if (*otrans == '\\') { if (*(++otrans) == '\0') break; else continue; } if (*otrans == ',') { otrans++; break; } next[i++] = *otrans++; if (i >= MAX_REALM_LN) { retval = KRB5KRB_AP_ERR_ILL_CR_TKT; goto fail; } } next[i] = '\0'; nlst = i - 1; if (!strcmp(exp, realm)) added = TRUE; /* If we still have to insert the new realm */ if (!added) { /* Is the next field compressed? If not, and if the new */ /* realm is a subrealm of the current realm, compress */ /* the new realm, and insert immediately following the */ /* current one. Note that we can not do this if the next*/ /* field is already compressed since it would mess up */ /* what has already been done. In most cases, this is */ /* not a problem because the realm to be added will be a */ /* subrealm of the next field too, and we will catch */ /* it in a future iteration. */ /* Note that the second test here is an unsigned comparison, so the first half (or a cast) is also required. */ assert(nlst < 0 || nlst < sizeof(next)); if ((nlst < 0 || next[nlst] != '.') && (next[0] != '/') && (pl = subrealm(exp, realm))) { added = TRUE; current[sizeof(current) - 1] = '\0'; if (strlen(current) + (pl>0?pl:-pl) + 2 >= MAX_REALM_LN) { retval = KRB5KRB_AP_ERR_ILL_CR_TKT; goto fail; } strncat(current, ",", sizeof(current) - 1 - strlen(current)); if (pl > 0) { strncat(current, realm, (unsigned) pl); } else { strncat(current, realm+strlen(realm)+pl, (unsigned) (-pl)); } } /* Whether or not the next field is compressed, if the */ /* realm to be added is a superrealm of the current realm,*/ /* then the current realm can be compressed. First the */ /* realm to be added must be compressed relative to the */ /* previous realm (if possible), and then the current */ /* realm compressed relative to the new realm. Note that */ /* if the realm to be added is also a superrealm of the */ /* previous realm, it would have been added earlier, and */ /* we would not reach this step this time around. */ else if ((pl = subrealm(realm, exp))) { added = TRUE; current[0] = '\0'; if ((pl1 = subrealm(prev,realm))) { if (strlen(current) + (pl1>0?pl1:-pl1) + 1 >= MAX_REALM_LN) { retval = KRB5KRB_AP_ERR_ILL_CR_TKT; goto fail; } if (pl1 > 0) { strncat(current, realm, (unsigned) pl1); } else { strncat(current, realm+strlen(realm)+pl1, (unsigned) (-pl1)); } } else { /* If not a subrealm */ if ((realm[0] == '/') && prev[0]) { if (strlen(current) + 2 >= MAX_REALM_LN) { retval = KRB5KRB_AP_ERR_ILL_CR_TKT; goto fail; } strncat(current, " ", sizeof(current) - 1 - strlen(current)); current[sizeof(current) - 1] = '\0'; } if (strlen(current) + strlen(realm) + 1 >= MAX_REALM_LN) { retval = KRB5KRB_AP_ERR_ILL_CR_TKT; goto fail; } strncat(current, realm, sizeof(current) - 1 - strlen(current)); current[sizeof(current) - 1] = '\0'; } if (strlen(current) + (pl>0?pl:-pl) + 2 >= MAX_REALM_LN) { retval = KRB5KRB_AP_ERR_ILL_CR_TKT; goto fail; } strncat(current,",", sizeof(current) - 1 - strlen(current)); current[sizeof(current) - 1] = '\0'; if (pl > 0) { strncat(current, exp, (unsigned) pl); } else { strncat(current, exp+strlen(exp)+pl, (unsigned)(-pl)); } } } if (new_trans->length != 0) { if (strlen(trans) + 2 >= MAX_REALM_LN) { retval = KRB5KRB_AP_ERR_ILL_CR_TKT; goto fail; } strcat(trans, ","); } if (strlen(trans) + strlen(current) + 1 >= MAX_REALM_LN) { retval = KRB5KRB_AP_ERR_ILL_CR_TKT; goto fail; } strcat(trans, current); new_trans->length = strlen(trans); strncpy(prev, exp, sizeof(prev) - 1); prev[sizeof(prev) - 1] = '\0'; strncpy(current, next, sizeof(current) - 1); current[sizeof(current) - 1] = '\0'; } if (!added) { if (new_trans->length != 0) { if (strlen(trans) + 2 >= MAX_REALM_LN) { retval = KRB5KRB_AP_ERR_ILL_CR_TKT; goto fail; } strcat(trans, ","); } if((realm[0] == '/') && trans[0]) { if (strlen(trans) + 2 >= MAX_REALM_LN) { retval = KRB5KRB_AP_ERR_ILL_CR_TKT; goto fail; } strcat(trans, " "); } if (strlen(trans) + strlen(realm) + 1 >= MAX_REALM_LN) { retval = KRB5KRB_AP_ERR_ILL_CR_TKT; goto fail; } strcat(trans, realm); new_trans->length = strlen(trans); } retval = 0; fail: free(realm); free(otrans_ptr); return (retval); } /* * Routines that validate a AS request; checks a lot of things. :-) * * Returns a Kerberos protocol error number, which is _not_ the same * as a com_err error number! */ #define AS_INVALID_OPTIONS (KDC_OPT_FORWARDED | KDC_OPT_PROXY |\ KDC_OPT_VALIDATE | KDC_OPT_RENEW | KDC_OPT_ENC_TKT_IN_SKEY) int validate_as_request(register krb5_kdc_req *request, krb5_db_entry client, krb5_db_entry server, krb5_timestamp kdc_time, const char **status) { int errcode; /* * If an option is set that is only allowed in TGS requests, complain. */ if (request->kdc_options & AS_INVALID_OPTIONS) { *status = "INVALID AS OPTIONS"; return KDC_ERR_BADOPTION; } /* The client's password must not be expired, unless the server is a KRB5_KDC_PWCHANGE_SERVICE. */ if (client.pw_expiration && client.pw_expiration < kdc_time && !isflagset(server.attributes, KRB5_KDB_PWCHANGE_SERVICE)) { *status = "CLIENT KEY EXPIRED"; #ifdef KRBCONF_VAGUE_ERRORS return(KRB_ERR_GENERIC); #else return(KDC_ERR_KEY_EXP); #endif } /* The client must not be expired */ if (client.expiration && client.expiration < kdc_time) { *status = "CLIENT EXPIRED"; #ifdef KRBCONF_VAGUE_ERRORS return(KRB_ERR_GENERIC); #else return(KDC_ERR_NAME_EXP); #endif } /* The server must not be expired */ if (server.expiration && server.expiration < kdc_time) { *status = "SERVICE EXPIRED"; return(KDC_ERR_SERVICE_EXP); } /* * If the client requires password changing, then only allow the * pwchange service. */ if (isflagset(client.attributes, KRB5_KDB_REQUIRES_PWCHANGE) && !isflagset(server.attributes, KRB5_KDB_PWCHANGE_SERVICE)) { *status = "REQUIRED PWCHANGE"; return(KDC_ERR_KEY_EXP); } /* Client and server must allow postdating tickets */ if ((isflagset(request->kdc_options, KDC_OPT_ALLOW_POSTDATE) || isflagset(request->kdc_options, KDC_OPT_POSTDATED)) && (isflagset(client.attributes, KRB5_KDB_DISALLOW_POSTDATED) || isflagset(server.attributes, KRB5_KDB_DISALLOW_POSTDATED))) { *status = "POSTDATE NOT ALLOWED"; return(KDC_ERR_CANNOT_POSTDATE); } /* Client and server must allow forwardable tickets */ if (isflagset(request->kdc_options, KDC_OPT_FORWARDABLE) && (isflagset(client.attributes, KRB5_KDB_DISALLOW_FORWARDABLE) || isflagset(server.attributes, KRB5_KDB_DISALLOW_FORWARDABLE))) { *status = "FORWARDABLE NOT ALLOWED"; return(KDC_ERR_POLICY); } /* Client and server must allow renewable tickets */ if (isflagset(request->kdc_options, KDC_OPT_RENEWABLE) && (isflagset(client.attributes, KRB5_KDB_DISALLOW_RENEWABLE) || isflagset(server.attributes, KRB5_KDB_DISALLOW_RENEWABLE))) { *status = "RENEWABLE NOT ALLOWED"; return(KDC_ERR_POLICY); } /* Client and server must allow proxiable tickets */ if (isflagset(request->kdc_options, KDC_OPT_PROXIABLE) && (isflagset(client.attributes, KRB5_KDB_DISALLOW_PROXIABLE) || isflagset(server.attributes, KRB5_KDB_DISALLOW_PROXIABLE))) { *status = "PROXIABLE NOT ALLOWED"; return(KDC_ERR_POLICY); } /* Check to see if client is locked out */ if (isflagset(client.attributes, KRB5_KDB_DISALLOW_ALL_TIX)) { *status = "CLIENT LOCKED OUT"; return(KDC_ERR_C_PRINCIPAL_UNKNOWN); } /* Check to see if server is locked out */ if (isflagset(server.attributes, KRB5_KDB_DISALLOW_ALL_TIX)) { *status = "SERVICE LOCKED OUT"; return(KDC_ERR_S_PRINCIPAL_UNKNOWN); } /* Check to see if server is allowed to be a service */ if (isflagset(server.attributes, KRB5_KDB_DISALLOW_SVR)) { *status = "SERVICE NOT ALLOWED"; return(KDC_ERR_S_PRINCIPAL_UNKNOWN); } /* * Check against local policy */ errcode = against_local_policy_as(request, server, client, kdc_time, status); if (errcode) return errcode; return 0; } #define ASN1_ID_CLASS (0xc0) #define ASN1_ID_TYPE (0x20) #define ASN1_ID_TAG (0x1f) #define ASN1_CLASS_UNIV (0) #define ASN1_CLASS_APP (1) #define ASN1_CLASS_CTX (2) #define ASN1_CLASS_PRIV (3) #define asn1_id_constructed(x) (x & ASN1_ID_TYPE) #define asn1_id_primitive(x) (!asn1_id_constructed(x)) #define asn1_id_class(x) ((x & ASN1_ID_CLASS) >> 6) #define asn1_id_tag(x) (x & ASN1_ID_TAG) /* * asn1length - return encoded length of value. * * passed a pointer into the asn.1 stream, which is updated * to point right after the length bits. * * returns -1 on failure. */ static int asn1length(unsigned char **astream) { int length; /* resulting length */ int sublen; /* sublengths */ int blen; /* bytes of length */ unsigned char *p; /* substring searching */ if (**astream & 0x80) { blen = **astream & 0x7f; if (blen > 3) { return(-1); } for (++*astream, length = 0; blen; ++*astream, blen--) { length = (length << 8) | **astream; } if (length == 0) { /* indefinite length, figure out by hand */ p = *astream; p++; while (1) { /* compute value length. */ if ((sublen = asn1length(&p)) < 0) { return(-1); } p += sublen; /* check for termination */ if ((!*p++) && (!*p)) { p++; break; } } length = p - *astream; } } else { length = **astream; ++*astream; } return(length); } /* * fetch_asn1_field - return raw asn.1 stream of subfield. * * this routine is passed a context-dependent tag number and "level" and returns * the size and length of the corresponding level subfield. * * levels and are numbered starting from 1. * * returns 0 on success, -1 otherwise. */ int fetch_asn1_field(unsigned char *astream, unsigned int level, unsigned int field, krb5_data *data) { unsigned char *estream; /* end of stream */ int classes; /* # classes seen so far this level */ unsigned int levels = 0; /* levels seen so far */ int lastlevel = 1000; /* last level seen */ int length; /* various lengths */ int tag; /* tag number */ unsigned char savelen; /* saved length of our field */ classes = -1; /* we assume that the first identifier/length will tell us how long the entire stream is. */ astream++; estream = astream; if ((length = asn1length(&astream)) < 0) { return(-1); } estream += length; /* search down the stream, checking identifiers. we process identifiers until we hit the "level" we want, and then process that level for our subfield, always making sure we don't go off the end of the stream. */ while (astream < estream) { if (!asn1_id_constructed(*astream)) { return(-1); } if (asn1_id_class(*astream) == ASN1_CLASS_CTX) { if ((tag = (int)asn1_id_tag(*astream)) <= lastlevel) { levels++; classes = -1; } lastlevel = tag; if (levels == level) { /* in our context-dependent class, is this the one we're looking for ? */ if (tag == field) { /* return length and data */ astream++; savelen = *astream; if ((data->length = asn1length(&astream)) < 0) { return(-1); } /* if the field length is indefinite, we will have to subtract two (terminating octets) from the length returned since we don't want to pass any info from the "wrapper" back. asn1length will always return the *total* length of the field, not just what's contained in it */ if ((savelen & 0xff) == 0x80) { data->length -=2 ; } data->data = (char *)astream; return(0); } else if (tag <= classes) { /* we've seen this class before, something must be wrong */ return(-1); } else { classes = tag; } } } /* if we're not on our level yet, process this value. otherwise skip over it */ astream++; if ((length = asn1length(&astream)) < 0) { return(-1); } if (levels == level) { astream += length; } } return(-1); } /* * Routines that validate a TGS request; checks a lot of things. :-) * * Returns a Kerberos protocol error number, which is _not_ the same * as a com_err error number! */ #define TGS_OPTIONS_HANDLED (KDC_OPT_FORWARDABLE | KDC_OPT_FORWARDED | \ KDC_OPT_PROXIABLE | KDC_OPT_PROXY | \ KDC_OPT_ALLOW_POSTDATE | KDC_OPT_POSTDATED | \ KDC_OPT_RENEWABLE | KDC_OPT_RENEWABLE_OK | \ KDC_OPT_ENC_TKT_IN_SKEY | KDC_OPT_RENEW | \ KDC_OPT_VALIDATE) #define NO_TGT_OPTION (KDC_OPT_FORWARDED | KDC_OPT_PROXY | KDC_OPT_RENEW | \ KDC_OPT_VALIDATE) int validate_tgs_request(register krb5_kdc_req *request, krb5_db_entry server, krb5_ticket *ticket, krb5_timestamp kdc_time, const char **status) { int errcode; int st_idx = 0; /* * If an illegal option is set, ignore it. */ request->kdc_options &= TGS_OPTIONS_HANDLED; /* Check to see if server has expired */ if (server.expiration && server.expiration < kdc_time) { *status = "SERVICE EXPIRED"; return(KDC_ERR_SERVICE_EXP); } /* * Verify that the server principal in authdat->ticket is correct * (either the ticket granting service or the service that was * originally requested) */ if (request->kdc_options & NO_TGT_OPTION) { if (!krb5_principal_compare(kdc_context, ticket->server, request->server)) { *status = "SERVER DIDN'T MATCH TICKET FOR RENEW/FORWARD/ETC"; return(KDC_ERR_SERVER_NOMATCH); } } else { /* * OK, we need to validate the krbtgt service in the ticket. * * The krbtgt service is of the form: * krbtgt/realm-A@realm-B * * Realm A is the "server realm"; the realm of the * server of the requested ticket must match this realm. * Of course, it should be a realm serviced by this KDC. * * Realm B is the "client realm"; this is what should be * added to the transited field. (which is done elsewhere) */ /* Make sure there are two components... */ if (krb5_princ_size(kdc_context, ticket->server) != 2) { *status = "BAD TGS SERVER LENGTH"; return KRB_AP_ERR_NOT_US; } /* ...that the first component is krbtgt... */ if (!krb5_is_tgs_principal(ticket->server)) { *status = "BAD TGS SERVER NAME"; return KRB_AP_ERR_NOT_US; } /* ...and that the second component matches the server realm... */ if ((krb5_princ_size(kdc_context, ticket->server) <= 1) || (krb5_princ_component(kdc_context, ticket->server, 1)->length != krb5_princ_realm(kdc_context, request->server)->length) || memcmp(krb5_princ_component(kdc_context, ticket->server, 1)->data, krb5_princ_realm(kdc_context, request->server)->data, krb5_princ_realm(kdc_context, request->server)->length)) { *status = "BAD TGS SERVER INSTANCE"; return KRB_AP_ERR_NOT_US; } /* XXX add check that second component must match locally * supported realm? */ /* Server must allow TGS based issuances */ if (isflagset(server.attributes, KRB5_KDB_DISALLOW_TGT_BASED)) { *status = "TGT BASED NOT ALLOWED"; return(KDC_ERR_POLICY); } } /* TGS must be forwardable to get forwarded or forwardable ticket */ if ((isflagset(request->kdc_options, KDC_OPT_FORWARDED) || isflagset(request->kdc_options, KDC_OPT_FORWARDABLE)) && !isflagset(ticket->enc_part2->flags, TKT_FLG_FORWARDABLE)) { *status = "TGT NOT FORWARDABLE"; return KDC_ERR_BADOPTION; } /* TGS must be proxiable to get proxiable ticket */ if ((isflagset(request->kdc_options, KDC_OPT_PROXY) || isflagset(request->kdc_options, KDC_OPT_PROXIABLE)) && !isflagset(ticket->enc_part2->flags, TKT_FLG_PROXIABLE)) { *status = "TGT NOT PROXIABLE"; return KDC_ERR_BADOPTION; } /* TGS must allow postdating to get postdated ticket */ if ((isflagset(request->kdc_options, KDC_OPT_ALLOW_POSTDATE) || isflagset(request->kdc_options, KDC_OPT_POSTDATED)) && !isflagset(ticket->enc_part2->flags, TKT_FLG_MAY_POSTDATE)) { *status = "TGT NOT POSTDATABLE"; return KDC_ERR_BADOPTION; } /* can only validate invalid tix */ if (isflagset(request->kdc_options, KDC_OPT_VALIDATE) && !isflagset(ticket->enc_part2->flags, TKT_FLG_INVALID)) { *status = "VALIDATE VALID TICKET"; return KDC_ERR_BADOPTION; } /* can only renew renewable tix */ if ((isflagset(request->kdc_options, KDC_OPT_RENEW) || isflagset(request->kdc_options, KDC_OPT_RENEWABLE)) && !isflagset(ticket->enc_part2->flags, TKT_FLG_RENEWABLE)) { *status = "TICKET NOT RENEWABLE"; return KDC_ERR_BADOPTION; } /* can not proxy ticket granting tickets */ if (isflagset(request->kdc_options, KDC_OPT_PROXY) && (!request->server->data || request->server->data[0].length != KRB5_TGS_NAME_SIZE || memcmp(request->server->data[0].data, KRB5_TGS_NAME, KRB5_TGS_NAME_SIZE))) { *status = "CAN'T PROXY TGT"; return KDC_ERR_BADOPTION; } /* Server must allow forwardable tickets */ if (isflagset(request->kdc_options, KDC_OPT_FORWARDABLE) && isflagset(server.attributes, KRB5_KDB_DISALLOW_FORWARDABLE)) { *status = "NON-FORWARDABLE TICKET"; return(KDC_ERR_POLICY); } /* Server must allow renewable tickets */ if (isflagset(request->kdc_options, KDC_OPT_RENEWABLE) && isflagset(server.attributes, KRB5_KDB_DISALLOW_RENEWABLE)) { *status = "NON-RENEWABLE TICKET"; return(KDC_ERR_POLICY); } /* Server must allow proxiable tickets */ if (isflagset(request->kdc_options, KDC_OPT_PROXIABLE) && isflagset(server.attributes, KRB5_KDB_DISALLOW_PROXIABLE)) { *status = "NON-PROXIABLE TICKET"; return(KDC_ERR_POLICY); } /* Server must allow postdated tickets */ if (isflagset(request->kdc_options, KDC_OPT_ALLOW_POSTDATE) && isflagset(server.attributes, KRB5_KDB_DISALLOW_POSTDATED)) { *status = "NON-POSTDATABLE TICKET"; return(KDC_ERR_CANNOT_POSTDATE); } /* Server must allow DUP SKEY requests */ if (isflagset(request->kdc_options, KDC_OPT_ENC_TKT_IN_SKEY) && isflagset(server.attributes, KRB5_KDB_DISALLOW_DUP_SKEY)) { *status = "DUP_SKEY DISALLOWED"; return(KDC_ERR_POLICY); } /* Server must not be locked out */ if (isflagset(server.attributes, KRB5_KDB_DISALLOW_ALL_TIX)) { *status = "SERVER LOCKED OUT"; return(KDC_ERR_S_PRINCIPAL_UNKNOWN); } /* Server must be allowed to be a service */ if (isflagset(server.attributes, KRB5_KDB_DISALLOW_SVR)) { *status = "SERVER NOT ALLOWED"; return(KDC_ERR_S_PRINCIPAL_UNKNOWN); } /* Check the hot list */ if (check_hot_list(ticket)) { *status = "HOT_LIST"; return(KRB_AP_ERR_REPEAT); } /* Check the start time vs. the KDC time */ if (isflagset(request->kdc_options, KDC_OPT_VALIDATE)) { if (ticket->enc_part2->times.starttime > kdc_time) { *status = "NOT_YET_VALID"; return(KRB_AP_ERR_TKT_NYV); } } /* * Check the renew_till time. The endtime was already * been checked in the initial authentication check. */ if (isflagset(request->kdc_options, KDC_OPT_RENEW) && (ticket->enc_part2->times.renew_till < kdc_time)) { *status = "TKT_EXPIRED"; return(KRB_AP_ERR_TKT_EXPIRED); } /* * Checks for ENC_TKT_IN_SKEY: * * (1) Make sure the second ticket exists * (2) Make sure it is a ticket granting ticket */ if (isflagset(request->kdc_options, KDC_OPT_ENC_TKT_IN_SKEY)) { if (!request->second_ticket || !request->second_ticket[st_idx]) { *status = "NO_2ND_TKT"; return(KDC_ERR_BADOPTION); } if (!krb5_principal_compare(kdc_context, request->second_ticket[st_idx]->server, tgs_server)) { *status = "2ND_TKT_NOT_TGS"; return(KDC_ERR_POLICY); } st_idx++; } /* Check for hardware preauthentication */ if (isflagset(server.attributes, KRB5_KDB_REQUIRES_HW_AUTH) && !isflagset(ticket->enc_part2->flags,TKT_FLG_HW_AUTH)) { *status = "NO HW PREAUTH"; return KRB_ERR_GENERIC; } /* Check for any kind of preauthentication */ if (isflagset(server.attributes, KRB5_KDB_REQUIRES_PRE_AUTH) && !isflagset(ticket->enc_part2->flags, TKT_FLG_PRE_AUTH)) { *status = "NO PREAUTH"; return KRB_ERR_GENERIC; } /* * Check local policy */ errcode = against_local_policy_tgs(request, server, ticket, status); if (errcode) return errcode; return 0; } /* * This function returns 1 if the dbentry has a key for a specified * keytype, and 0 if not. */ int dbentry_has_key_for_enctype(krb5_context context, krb5_db_entry *client, krb5_enctype enctype) { krb5_error_code retval; krb5_key_data *datap; retval = krb5_dbe_find_enctype(context, client, enctype, -1, 0, &datap); if (retval) return 0; else return 1; } /* * This function returns 1 if the entity referenced by this * structure can support the a particular encryption system, and 0 if * not. * * XXX eventually this information should be looked up in the * database. Since it isn't, we use some hueristics and attribute * options bits for now. */ int dbentry_supports_enctype(krb5_context context, krb5_db_entry *client, krb5_enctype enctype) { /* * If it's DES_CBC_MD5, there's a bit in the attribute mask which * checks to see if we support it. * * In theory everything's supposed to support DES_CBC_MD5, but * that's not the reality.... */ /* * We are assuming that all entries can support MD5; this information * need not be kept in the database. */ if (enctype == ENCTYPE_DES_CBC_MD5) return 1; /* * XXX we assume everything can understand DES_CBC_CRC */ if (enctype == ENCTYPE_DES_CBC_CRC) return 1; /* * If we have a key for the encryption system, we assume it's * supported. */ return dbentry_has_key_for_enctype(context, client, enctype); } /* * This function returns the keytype which should be selected for the * session key. It is based on the ordered list which the user * requested, and what the KDC and the application server can support. */ krb5_enctype select_session_keytype(krb5_context context, krb5_db_entry *server, int nktypes, krb5_enctype *ktype) { int i; for (i = 0; i < nktypes; i++) { if (!krb5_c_valid_enctype(ktype[i])) continue; if (!krb5_is_permitted_enctype(context, ktype[i])) continue; if (dbentry_supports_enctype(context, server, ktype[i])) return ktype[i]; } return 0; } /* * This function returns salt information for a particular client_key */ krb5_error_code get_salt_from_key(krb5_context context, krb5_principal client, krb5_key_data *client_key, krb5_data *salt) { krb5_error_code retval; krb5_data * realm; salt->data = 0; salt->length = SALT_TYPE_NO_LENGTH; if (client_key->key_data_ver == 1) return 0; switch (client_key->key_data_type[1]) { case KRB5_KDB_SALTTYPE_NORMAL: break; case KRB5_KDB_SALTTYPE_V4: /* send an empty (V4) salt */ salt->data = 0; salt->length = 0; break; case KRB5_KDB_SALTTYPE_NOREALM: if ((retval = krb5_principal2salt_norealm(context, client, salt))) return retval; break; case KRB5_KDB_SALTTYPE_AFS3: /* send the same salt as with onlyrealm - but with no type info, we just hope they figure it out on the other end. */ /* fall through to onlyrealm: */ case KRB5_KDB_SALTTYPE_ONLYREALM: realm = krb5_princ_realm(context, client); salt->length = realm->length; if ((salt->data = malloc(realm->length)) == NULL) return ENOMEM; memcpy(salt->data, realm->data, realm->length); break; case KRB5_KDB_SALTTYPE_SPECIAL: salt->length = client_key->key_data_length[1]; if ((salt->data = malloc(salt->length)) == NULL) return ENOMEM; memcpy(salt->data, client_key->key_data_contents[1], salt->length); break; } return 0; } /* * Limit strings to a "reasonable" length to prevent crowding out of * other useful information in the log entry */ #define NAME_LENGTH_LIMIT 128 void limit_string(char *name) { int i; if (!name) return; if (strlen(name) < NAME_LENGTH_LIMIT) return; i = NAME_LENGTH_LIMIT-4; name[i++] = '.'; name[i++] = '.'; name[i++] = '.'; name[i] = '\0'; return; } /* * L10_2 = log10(2**x), rounded up; log10(2) ~= 0.301. */ #define L10_2(x) ((int)(((x * 301) + 999) / 1000)) /* * Max length of sprintf("%ld") for an int of type T; includes leading * minus sign and terminating NUL. */ #define D_LEN(t) (L10_2(sizeof(t) * CHAR_BIT) + 2) void ktypes2str(char *s, size_t len, int nktypes, krb5_enctype *ktype) { int i; char stmp[D_LEN(krb5_enctype) + 1]; char *p; if (nktypes < 0 || len < (sizeof(" etypes {...}") + D_LEN(int))) { *s = '\0'; return; } sprintf(s, "%d etypes {", nktypes); for (i = 0; i < nktypes; i++) { sprintf(stmp, "%s%ld", i ? " " : "", (long)ktype[i]); if (strlen(s) + strlen(stmp) + sizeof("}") > len) break; strcat(s, stmp); } if (i < nktypes) { /* * We broke out of the loop. Try to truncate the list. */ p = s + strlen(s); while (p - s + sizeof("...}") > len) { while (p > s && *p != ' ' && *p != '{') *p-- = '\0'; if (p > s && *p == ' ') { *p-- = '\0'; continue; } } strcat(s, "..."); } strcat(s, "}"); return; } void rep_etypes2str(char *s, size_t len, krb5_kdc_rep *rep) { char stmp[sizeof("ses=") + D_LEN(krb5_enctype)]; if (len < (3 * D_LEN(krb5_enctype) + sizeof("etypes {rep= tkt= ses=}"))) { *s = '\0'; return; } sprintf(s, "etypes {rep=%ld", (long)rep->enc_part.enctype); if (rep->ticket != NULL) { sprintf(stmp, " tkt=%ld", (long)rep->ticket->enc_part.enctype); strcat(s, stmp); } if (rep->ticket != NULL && rep->ticket->enc_part2 != NULL && rep->ticket->enc_part2->session != NULL) { sprintf(stmp, " ses=%ld", (long)rep->ticket->enc_part2->session->enctype); strcat(s, stmp); } strcat(s, "}"); return; } /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * kdc/kdc_util.h * * Copyright 1990 by the Massachusetts Institute of Technology. * * Export of this software from the United States of America may * require a specific license from the United States Government. * It is the responsibility of any person or organization contemplating * export to obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of M.I.T. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. Furthermore if you modify this software you must label * your software as modified software and not distribute it in such a * fashion that it might be confused with the original M.I.T. software. * M.I.T. makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. * * * Declarations for policy.c */ #ifndef __KRB5_KDC_UTIL__ #define __KRB5_KDC_UTIL__ #include "kdb.h" #ifdef __cplusplus extern "C" { #endif typedef struct _krb5_fulladdr { krb5_address * address; krb5_ui_4 port; } krb5_fulladdr; krb5_error_code check_hot_list (krb5_ticket *); krb5_boolean realm_compare (krb5_principal, krb5_principal); krb5_boolean krb5_is_tgs_principal (krb5_principal); krb5_error_code add_to_transited (krb5_data *, krb5_data *, krb5_principal, krb5_principal, krb5_principal); krb5_error_code compress_transited (krb5_data *, krb5_principal, krb5_data *); krb5_error_code concat_authorization_data (krb5_authdata **, krb5_authdata **, krb5_authdata ***); krb5_error_code fetch_last_req_info (krb5_db_entry *, krb5_last_req_entry ***); krb5_error_code kdc_convert_key (krb5_keyblock *, krb5_keyblock *, int); krb5_error_code kdc_process_tgs_req (krb5_kdc_req *, const krb5_fulladdr *, krb5_data *, krb5_ticket **, krb5_keyblock **); krb5_error_code kdc_get_server_key (krb5_ticket *, krb5_keyblock **, krb5_kvno *); int validate_as_request (krb5_kdc_req *, krb5_db_entry, krb5_db_entry, krb5_timestamp, const char **); int validate_tgs_request (krb5_kdc_req *, krb5_db_entry, krb5_ticket *, krb5_timestamp, const char **); int fetch_asn1_field (unsigned char *, unsigned int, unsigned int, krb5_data *); int dbentry_has_key_for_enctype (krb5_context context, krb5_db_entry *client, krb5_enctype enctype); int dbentry_supports_enctype (krb5_context context, krb5_db_entry *client, krb5_enctype enctype); krb5_enctype select_session_keytype (krb5_context context, krb5_db_entry *server, int nktypes, krb5_enctype *ktypes); krb5_error_code get_salt_from_key (krb5_context, krb5_principal, krb5_key_data *, krb5_data *); void limit_string (char *name); void ktypes2str(char *s, size_t len, int nktypes, krb5_enctype *ktype); void rep_etypes2str(char *s, size_t len, krb5_kdc_rep *rep); /* do_as_req.c */ krb5_error_code process_as_req (krb5_kdc_req *, krb5_data *, const krb5_fulladdr *, krb5_data ** ); /* do_tgs_req.c */ krb5_error_code process_tgs_req (krb5_data *, const krb5_fulladdr *, krb5_data ** ); /* dispatch.c */ krb5_error_code dispatch (krb5_data *, const krb5_fulladdr *, krb5_data **); /* main.c */ krb5_error_code kdc_initialize_rcache (krb5_context, char *); krb5_error_code setup_server_realm (krb5_principal); /* network.c */ krb5_error_code listen_and_process (const char *); krb5_error_code setup_network (const char *); krb5_error_code closedown_network (const char *); /* policy.c */ int against_local_policy_as (krb5_kdc_req *, krb5_db_entry, krb5_db_entry, krb5_timestamp, const char **); int against_local_policy_tgs (krb5_kdc_req *, krb5_db_entry, krb5_ticket *, const char **); /* kdc_preauth.c */ const char * missing_required_preauth (krb5_db_entry *client, krb5_db_entry *server, krb5_enc_tkt_part *enc_tkt_reply); void get_preauth_hint_list (krb5_kdc_req * request, krb5_db_entry *client, krb5_db_entry *server, krb5_data *e_data); krb5_error_code load_preauth_plugins(krb5_context context); krb5_error_code unload_preauth_plugins(krb5_context context); krb5_error_code check_padata (krb5_context context, krb5_db_entry *client, krb5_data *req_pkt, krb5_kdc_req *request, krb5_enc_tkt_part *enc_tkt_reply, void **padata_context, krb5_data *e_data); krb5_error_code return_padata (krb5_context context, krb5_db_entry *client, krb5_data *req_pkt, krb5_kdc_req *request, krb5_kdc_rep *reply, krb5_key_data *client_key, krb5_keyblock *encrypting_key, void **padata_context); krb5_error_code free_padata_context (krb5_context context, void **padata_context); /* replay.c */ krb5_boolean kdc_check_lookaside (krb5_data *, krb5_data **); void kdc_insert_lookaside (krb5_data *, krb5_data *); void kdc_free_lookaside(krb5_context); /* which way to convert key? */ #define CONVERT_INTO_DB 0 #define CONVERT_OUTOF_DB 1 #define isflagset(flagfield, flag) (flagfield & (flag)) #define setflag(flagfield, flag) (flagfield |= (flag)) #define clear(flagfield, flag) (flagfield &= ~(flag)) #ifdef KRB5_KRB4_COMPAT krb5_error_code process_v4 (const krb5_data *, const krb5_fulladdr *, krb5_data **); void process_v4_mode (const char *, const char *); void enable_v4_crossrealm(char *); #else #define process_v4(foo,bar,quux,foobar) KRB5KRB_AP_ERR_BADVERSION #endif #ifndef min #define min(a, b) ((a) < (b) ? (a) : (b)) #define max(a, b) ((a) > (b) ? (a) : (b)) #endif #ifdef KRB5_USE_INET6 #define ADDRTYPE2FAMILY(X) \ ((X) == ADDRTYPE_INET6 ? AF_INET6 : (X) == ADDRTYPE_INET ? AF_INET : -1) #else #define ADDRTYPE2FAMILY(X) \ ((X) == ADDRTYPE_INET ? AF_INET : -1) #endif /* RFC 4120: KRB5KDC_ERR_KEY_TOO_WEAK * RFC 4556: KRB5KDC_ERR_DH_KEY_PARAMETERS_NOT_ACCEPTED */ #define KRB5KDC_ERR_KEY_TOO_WEAK KRB5KDC_ERR_DH_KEY_PARAMETERS_NOT_ACCEPTED #ifdef __cplusplus } #endif #endif /* !__KRB5_KDC_UTIL__ */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * kdc/main.c * * Copyright 1990,2001 by the Massachusetts Institute of Technology. * * Export of this software from the United States of America may * require a specific license from the United States Government. * It is the responsibility of any person or organization contemplating * export to obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of M.I.T. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. Furthermore if you modify this software you must label * your software as modified software and not distribute it in such a * fashion that it might be confused with the original M.I.T. software. * M.I.T. makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. * * * Main procedure body for the KDC server process. */ #include #include #include #include #include #include "k5-int.h" #include "com_err.h" #include "adm.h" #include "adm_proto.h" #include "kdc_util.h" #include "extern.h" #include "kdc5_err.h" #include #include #ifdef HAVE_NETINET_IN_H #include #endif #ifdef KRB5_KRB4_COMPAT #include #endif #if defined(NEED_DAEMON_PROTO) extern int daemon(int, int); #endif void usage (char *); krb5_sigtype request_exit (int); krb5_sigtype request_hup (int); void setup_signal_handlers (void); krb5_error_code setup_sam (void); void initialize_realms (krb5_context, int, char **); void finish_realms (char *); static int nofork = 0; static int rkey_init_done = 0; /* Solaris Kerberos: global here that other functions access */ int max_tcp_data_connections; #ifdef POSIX_SIGNALS static struct sigaction s_action; #endif /* POSIX_SIGNALS */ #define KRB5_KDC_MAX_REALMS 32 /* * Find the realm entry for a given realm. */ kdc_realm_t * find_realm_data(char *rname, krb5_ui_4 rsize) { int i; for (i=0; irealm_name)) && !strncmp(rname, kdc_realmlist[i]->realm_name, rsize)) return(kdc_realmlist[i]); } return((kdc_realm_t *) NULL); } krb5_error_code setup_server_realm(krb5_principal sprinc) { krb5_error_code kret; kdc_realm_t *newrealm; kret = 0; if (kdc_numrealms > 1) { if (!(newrealm = find_realm_data(sprinc->realm.data, (krb5_ui_4) sprinc->realm.length))) kret = ENOENT; else kdc_active_realm = newrealm; } else kdc_active_realm = kdc_realmlist[0]; return(kret); } static void finish_realm(kdc_realm_t *rdp) { if (rdp->realm_dbname) free(rdp->realm_dbname); if (rdp->realm_mpname) free(rdp->realm_mpname); if (rdp->realm_stash) free(rdp->realm_stash); if (rdp->realm_ports) free(rdp->realm_ports); if (rdp->realm_tcp_ports) free(rdp->realm_tcp_ports); if (rdp->realm_keytab) krb5_kt_close(rdp->realm_context, rdp->realm_keytab); if (rdp->realm_context) { if (rdp->realm_mprinc) krb5_free_principal(rdp->realm_context, rdp->realm_mprinc); if (rdp->realm_mkey.length && rdp->realm_mkey.contents) { memset(rdp->realm_mkey.contents, 0, rdp->realm_mkey.length); free(rdp->realm_mkey.contents); } krb5_db_fini(rdp->realm_context); if (rdp->realm_tgsprinc) krb5_free_principal(rdp->realm_context, rdp->realm_tgsprinc); krb5_free_context(rdp->realm_context); } memset((char *) rdp, 0, sizeof(*rdp)); free(rdp); } /* * Initialize a realm control structure from the alternate profile or from * the specified defaults. * * After we're complete here, the essence of the realm is embodied in the * realm data and we should be all set to begin operation for that realm. */ static krb5_error_code init_realm(krb5_context kcontext, char *progname, kdc_realm_t *rdp, char *realm, char *def_mpname, krb5_enctype def_enctype, char *def_udp_ports, char *def_tcp_ports, krb5_boolean def_manual, char **db_args) { krb5_error_code kret; krb5_boolean manual; krb5_realm_params *rparams; memset((char *) rdp, 0, sizeof(kdc_realm_t)); if (!realm) { kret = EINVAL; goto whoops; } rdp->realm_name = realm; kret = krb5int_init_context_kdc(&rdp->realm_context); if (kret) { com_err(progname, kret, gettext("while getting context for realm %s"), realm); goto whoops; } /* * Solaris Kerberos: * Set the current context to that of the realm being init'ed */ krb5_klog_set_context(rdp->realm_context); kret = krb5_read_realm_params(rdp->realm_context, rdp->realm_name, &rparams); if (kret) { com_err(progname, kret, gettext("while reading realm parameters")); goto whoops; } /* Handle profile file name */ if (rparams && rparams->realm_profile) rdp->realm_profile = strdup(rparams->realm_profile); /* Handle master key name */ if (rparams && rparams->realm_mkey_name) rdp->realm_mpname = strdup(rparams->realm_mkey_name); else rdp->realm_mpname = (def_mpname) ? strdup(def_mpname) : strdup(KRB5_KDB_M_NAME); /* Handle KDC ports */ if (rparams && rparams->realm_kdc_ports) rdp->realm_ports = strdup(rparams->realm_kdc_ports); else rdp->realm_ports = strdup(def_udp_ports); if (rparams && rparams->realm_kdc_tcp_ports) rdp->realm_tcp_ports = strdup(rparams->realm_kdc_tcp_ports); else rdp->realm_tcp_ports = strdup(def_tcp_ports); /* Handle stash file */ if (rparams && rparams->realm_stash_file) { rdp->realm_stash = strdup(rparams->realm_stash_file); manual = FALSE; } else manual = def_manual; /* Handle master key type */ if (rparams && rparams->realm_enctype_valid) rdp->realm_mkey.enctype = (krb5_enctype) rparams->realm_enctype; else rdp->realm_mkey.enctype = manual ? def_enctype : ENCTYPE_UNKNOWN; /* Handle reject-bad-transit flag */ if (rparams && rparams->realm_reject_bad_transit_valid) rdp->realm_reject_bad_transit = rparams->realm_reject_bad_transit; else rdp->realm_reject_bad_transit = 1; /* Handle ticket maximum life */ rdp->realm_maxlife = (rparams && rparams->realm_max_life_valid) ? rparams->realm_max_life : KRB5_KDB_MAX_LIFE; /* Handle ticket renewable maximum life */ rdp->realm_maxrlife = (rparams && rparams->realm_max_rlife_valid) ? rparams->realm_max_rlife : KRB5_KDB_MAX_RLIFE; if (rparams) krb5_free_realm_params(rdp->realm_context, rparams); /* * We've got our parameters, now go and setup our realm context. */ /* Set the default realm of this context */ if ((kret = krb5_set_default_realm(rdp->realm_context, realm))) { com_err(progname, kret, gettext("while setting default realm to %s"), realm); goto whoops; } /* first open the database before doing anything */ #ifdef KRBCONF_KDC_MODIFIES_KDB if ((kret = krb5_db_open(rdp->realm_context, db_args, KRB5_KDB_OPEN_RW | KRB5_KDB_SRV_TYPE_KDC))) { #else if ((kret = krb5_db_open(rdp->realm_context, db_args, KRB5_KDB_OPEN_RO | KRB5_KDB_SRV_TYPE_KDC))) { #endif /* * Solaris Kerberos: * Make sure that error messages are printed using gettext */ com_err(progname, kret, gettext("while initializing database for realm %s"), realm); goto whoops; } /* Assemble and parse the master key name */ if ((kret = krb5_db_setup_mkey_name(rdp->realm_context, rdp->realm_mpname, rdp->realm_name, (char **) NULL, &rdp->realm_mprinc))) { com_err(progname, kret, gettext("while setting up master key name %s for realm %s"), rdp->realm_mpname, realm); goto whoops; } /* * Get the master key. */ if ((kret = krb5_db_fetch_mkey(rdp->realm_context, rdp->realm_mprinc, rdp->realm_mkey.enctype, manual, FALSE, rdp->realm_stash, 0, &rdp->realm_mkey))) { com_err(progname, kret, gettext("while fetching master key %s for realm %s"), rdp->realm_mpname, realm); goto whoops; } /* Verify the master key */ if ((kret = krb5_db_verify_master_key(rdp->realm_context, rdp->realm_mprinc, &rdp->realm_mkey))) { com_err(progname, kret, gettext("while verifying master key for realm %s"), realm); goto whoops; } if ((kret = krb5_db_set_mkey(rdp->realm_context, &rdp->realm_mkey))) { com_err(progname, kret, gettext("while processing master key for realm %s"), realm); goto whoops; } /* Set up the keytab */ if ((kret = krb5_ktkdb_resolve(rdp->realm_context, NULL, &rdp->realm_keytab))) { com_err(progname, kret, gettext("while resolving kdb keytab for realm %s"), realm); goto whoops; } /* Preformat the TGS name */ if ((kret = krb5_build_principal(rdp->realm_context, &rdp->realm_tgsprinc, strlen(realm), realm, KRB5_TGS_NAME, realm, (char *) NULL))) { com_err(progname, kret, gettext("while building TGS name for realm %s"), realm); goto whoops; } if (!rkey_init_done) { #ifdef KRB5_KRB4_COMPAT krb5_keyblock temp_key; #endif /* * If all that worked, then initialize the random key * generators. */ #ifdef KRB5_KRB4_COMPAT if ((kret = krb5_c_make_random_key(rdp->realm_context, ENCTYPE_DES_CBC_CRC, &temp_key))) { com_err(progname, kret, "while initializing V4 random key generator"); goto whoops; } (void) des_init_random_number_generator(temp_key.contents); krb5_free_keyblock_contents(rdp->realm_context, &temp_key); #endif rkey_init_done = 1; } whoops: /* * If we choked, then clean up any dirt we may have dropped on the floor. */ if (kret) { finish_realm(rdp); } /* * Solaris Kerberos: * Set the current context back to the general context */ krb5_klog_set_context(kcontext); return(kret); } krb5_sigtype request_exit(int signo) { signal_requests_exit = 1; #ifdef POSIX_SIGTYPE return; #else return(0); #endif } krb5_sigtype request_hup(int signo) { signal_requests_hup = 1; #ifdef POSIX_SIGTYPE return; #else return(0); #endif } void setup_signal_handlers(void) { #ifdef POSIX_SIGNALS (void) sigemptyset(&s_action.sa_mask); s_action.sa_flags = 0; s_action.sa_handler = request_exit; (void) sigaction(SIGINT, &s_action, (struct sigaction *) NULL); (void) sigaction(SIGTERM, &s_action, (struct sigaction *) NULL); s_action.sa_handler = request_hup; (void) sigaction(SIGHUP, &s_action, (struct sigaction *) NULL); s_action.sa_handler = SIG_IGN; (void) sigaction(SIGPIPE, &s_action, (struct sigaction *) NULL); #else /* POSIX_SIGNALS */ signal(SIGINT, request_exit); signal(SIGTERM, request_exit); signal(SIGHUP, request_hup); signal(SIGPIPE, SIG_IGN); #endif /* POSIX_SIGNALS */ return; } krb5_error_code setup_sam(void) { return krb5_c_make_random_key(kdc_context, ENCTYPE_DES_CBC_MD5, &psr_key); } void usage(char *name) { fprintf(stderr, gettext("usage: %s [-d dbpathname] [-r dbrealmname] [-R replaycachename ]\n\t[-m] [-k masterenctype] [-M masterkeyname] [-p port] [-n]\n"), name); fprintf(stderr, "usage: %s [-x db_args]* [-d dbpathname] [-r dbrealmname] [-R replaycachename ]\n\t[-m] [-k masterenctype] [-M masterkeyname] [-p port] [-X] [-n]\n" "\nwhere,\n\t[-x db_args]* - any number of database specific arguments.\n" "\t\t\tLook at each database documentation for supported arguments\n", name); return; } void initialize_realms(krb5_context kcontext, int argc, char **argv) { int c; char *db_name = (char *) NULL; char *mkey_name = (char *) NULL; char *rcname __unused; char *lrealm = NULL; krb5_error_code retval; krb5_enctype menctype = ENCTYPE_UNKNOWN; kdc_realm_t *rdatap; krb5_boolean manual = FALSE; char *default_udp_ports = 0; char *default_tcp_ports = 0; krb5_pointer aprof; const char *hierarchy[3]; char **db_args = NULL; int db_args_size = 0; #ifdef KRB5_KRB4_COMPAT char *v4mode = 0; #endif extern char *optarg; rcname = KDCRCACHE; if (!krb5_aprof_init(DEFAULT_KDC_PROFILE, KDC_PROFILE_ENV, &aprof)) { hierarchy[0] = "kdcdefaults"; hierarchy[1] = "kdc_ports"; hierarchy[2] = (char *) NULL; if (krb5_aprof_get_string(aprof, hierarchy, TRUE, &default_udp_ports)) default_udp_ports = 0; hierarchy[1] = "kdc_tcp_ports"; if (krb5_aprof_get_string(aprof, hierarchy, TRUE, &default_tcp_ports)) default_tcp_ports = 0; hierarchy[1] = "kdc_max_tcp_connections"; if (krb5_aprof_get_int32(aprof, hierarchy, TRUE, &max_tcp_data_connections)) { max_tcp_data_connections = DEFAULT_KDC_TCP_CONNECTIONS; } else if (max_tcp_data_connections < MIN_KDC_TCP_CONNECTIONS) { max_tcp_data_connections = DEFAULT_KDC_TCP_CONNECTIONS; } #ifdef KRB5_KRB4_COMPAT hierarchy[1] = "v4_mode"; if (krb5_aprof_get_string(aprof, hierarchy, TRUE, &v4mode)) v4mode = 0; #endif /* aprof_init can return 0 with aprof == NULL */ if (aprof) krb5_aprof_finish(aprof); } if (default_udp_ports == 0) default_udp_ports = strdup(DEFAULT_KDC_UDP_PORTLIST); if (default_tcp_ports == 0) default_tcp_ports = strdup(DEFAULT_KDC_TCP_PORTLIST); /* * Loop through the option list. Each time we encounter a realm name, * use the previously scanned options to fill in for defaults. */ while ((c = getopt(argc, argv, "x:r:d:mM:k:R:e:p:s:n4:X3")) != -1) { switch(c) { case 'x': db_args_size++; { char **temp = realloc( db_args, sizeof(char*) * (db_args_size+1)); /* one for NULL */ if( temp == NULL ) { /* Solaris Kerberos: Keep error messages consistent */ com_err(argv[0], errno, gettext("while initializing KDC")); exit(1); } db_args = temp; } db_args[db_args_size-1] = optarg; db_args[db_args_size] = NULL; break; case 'r': /* realm name for db */ if (!find_realm_data(optarg, (krb5_ui_4) strlen(optarg))) { if ((rdatap = (kdc_realm_t *) malloc(sizeof(kdc_realm_t)))) { if ((retval = init_realm(kcontext, argv[0], rdatap, optarg, mkey_name, menctype, default_udp_ports, default_tcp_ports, manual, db_args))) { /* Solaris Kerberos: Keep error messages consistent */ com_err(argv[0], retval, gettext("while initializing realm %s"), optarg); exit(1); } kdc_realmlist[kdc_numrealms] = rdatap; kdc_numrealms++; free(db_args), db_args=NULL, db_args_size = 0; } else { /* Solaris Kerberos: Keep error messages consistent */ com_err(argv[0], errno, gettext("while initializing realm %s"), optarg); exit(1); } } break; case 'd': /* pathname for db */ /* now db_name is not a seperate argument. It has to be passed as part of the db_args */ if( db_name == NULL ) { db_name = malloc(sizeof("dbname=") + strlen(optarg)); if( db_name == NULL ) { /* Solaris Kerberos: Keep error messages consistent */ com_err(argv[0], errno, gettext("while initializing KDC")); exit(1); } sprintf( db_name, "dbname=%s", optarg); } db_args_size++; { char **temp = realloc( db_args, sizeof(char*) * (db_args_size+1)); /* one for NULL */ if( temp == NULL ) { /* Solaris Kerberos: Keep error messages consistent */ com_err(argv[0], errno, gettext("while initializing KDC")); exit(1); } db_args = temp; } db_args[db_args_size-1] = db_name; db_args[db_args_size] = NULL; break; case 'm': /* manual type-in of master key */ manual = TRUE; if (menctype == ENCTYPE_UNKNOWN) menctype = ENCTYPE_DES_CBC_CRC; break; case 'M': /* master key name in DB */ mkey_name = optarg; break; case 'n': nofork++; /* don't detach from terminal */ break; case 'k': /* enctype for master key */ /* Solaris Kerberos: Keep error messages consistent */ if (retval = krb5_string_to_enctype(optarg, &menctype)) com_err(argv[0], retval, gettext("while converting %s to an enctype"), optarg); break; case 'R': rcname = optarg; break; case 'p': if (default_udp_ports) free(default_udp_ports); default_udp_ports = strdup(optarg); if (default_tcp_ports) free(default_tcp_ports); default_tcp_ports = strdup(optarg); break; case '4': #ifdef KRB5_KRB4_COMPAT if (v4mode) free(v4mode); v4mode = strdup(optarg); #endif break; case 'X': #ifdef KRB5_KRB4_COMPAT enable_v4_crossrealm(argv[0]); #endif break; case '?': default: usage(argv[0]); exit(1); } } #ifdef KRB5_KRB4_COMPAT /* * Setup the v4 mode */ process_v4_mode(argv[0], v4mode); free(v4mode); #endif /* * Check to see if we processed any realms. */ if (kdc_numrealms == 0) { /* no realm specified, use default realm */ if ((retval = krb5_get_default_realm(kcontext, &lrealm))) { com_err(argv[0], retval, gettext("while attempting to retrieve default realm")); /* Solaris Kerberos: avoid double logging */ #if 0 fprintf (stderr, "%s: %s, %s", argv[0], error_message (retval), gettext("attempting to retrieve default realm\n")); #endif exit(1); } if ((rdatap = (kdc_realm_t *) malloc(sizeof(kdc_realm_t)))) { if ((retval = init_realm(kcontext, argv[0], rdatap, lrealm, mkey_name, menctype, default_udp_ports, default_tcp_ports, manual, db_args))) { /* Solaris Kerberos: Keep error messages consistent */ com_err(argv[0], retval, gettext("while initializing realm %s"), lrealm); exit(1); } kdc_realmlist[0] = rdatap; kdc_numrealms++; } else { if (lrealm) free(lrealm); } } #ifdef USE_RCACHE /* * Now handle the replay cache. */ if ((retval = kdc_initialize_rcache(kcontext, rcname))) { com_err(argv[0], retval, gettext("while initializing KDC replay cache '%s'"), rcname); exit(1); } #endif /* Ensure that this is set for our first request. */ kdc_active_realm = kdc_realmlist[0]; if (default_udp_ports) free(default_udp_ports); if (default_tcp_ports) free(default_tcp_ports); if (db_args) free(db_args); if (db_name) free(db_name); return; } void finish_realms(char *prog) { int i; for (i = 0; i < kdc_numrealms; i++) { finish_realm(kdc_realmlist[i]); kdc_realmlist[i] = 0; } } /* outline: process args & setup initialize database access (fetch master key, open DB) initialize network loop: listen for packet determine packet type, dispatch to handling routine (AS or TGS (or V4?)) reflect response exit on signal clean up secrets, close db shut down network exit */ int main(int argc, char **argv) { krb5_error_code retval; krb5_context kcontext; int errout = 0; krb5_boolean log_stderr_set; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ #define TEXT_DOMAIN "KRB5KDC_TEST" /* Use this only if it weren't */ #endif (void) textdomain(TEXT_DOMAIN); if (strrchr(argv[0], '/')) argv[0] = strrchr(argv[0], '/')+1; if (!(kdc_realmlist = (kdc_realm_t **) malloc(sizeof(kdc_realm_t *) * KRB5_KDC_MAX_REALMS))) { fprintf(stderr, gettext("%s: cannot get memory for realm list\n"), argv[0]); exit(1); } memset((char *) kdc_realmlist, 0, (size_t) (sizeof(kdc_realm_t *) * KRB5_KDC_MAX_REALMS)); /* * A note about Kerberos contexts: This context, "kcontext", is used * for the KDC operations, i.e. setup, network connection and error * reporting. The per-realm operations use the "realm_context" * associated with each realm. */ retval = krb5int_init_context_kdc(&kcontext); if (retval) { com_err(argv[0], retval, gettext("while initializing krb5")); exit(1); } krb5_klog_init(kcontext, "kdc", argv[0], 1); /* * Solaris Kerberos: * In the early stages of krb5kdc it is desirable to log error messages * to stderr as well as any other logging locations specified in config * files. */ log_stderr_set = krb5_klog_logging_to_stderr(); if (log_stderr_set != TRUE) { krb5_klog_add_stderr(); } /* initialize_kdc5_error_table(); SUNWresync121 XXX */ /* * Scan through the argument list */ initialize_realms(kcontext, argc, argv); setup_signal_handlers(); load_preauth_plugins(kcontext); retval = setup_sam(); if (retval) { com_err(argv[0], retval, gettext("while initializing SAM")); finish_realms(argv[0]); return 1; } if ((retval = setup_network(argv[0]))) { com_err(argv[0], retval, gettext("while initializing network")); finish_realms(argv[0]); return 1; } /* Solaris Kerberos: Remove the extra stderr logging */ if (log_stderr_set != TRUE) krb5_klog_remove_stderr(); /* * Solaris Kerberos: * List the logs (FILE, STDERR, etc) which are currently being * logged to and print that to stderr. Useful when trying to * track down a failure via SMF. */ if (retval = krb5_klog_list_logs(argv[0])) { com_err(argv[0], retval, gettext("while listing logs")); if (log_stderr_set != TRUE) { fprintf(stderr, gettext("%s: %s while listing logs\n"), argv[0], error_message(retval)); } } if (!nofork && daemon(0, 0)) { com_err(argv[0], errno, gettext("while detaching from tty")); if (log_stderr_set != TRUE) { fprintf(stderr, gettext("%s: %s while detaching from tty\n"), argv[0], strerror(errno)); } finish_realms(argv[0]); return 1; } if (retval = krb5_klog_syslog(LOG_INFO, "commencing operation")) { com_err(argv[0], retval, gettext("while logging message")); errout++; }; if ((retval = listen_and_process(argv[0]))) { com_err(argv[0], retval, gettext("while processing network requests")); errout++; } if ((retval = closedown_network(argv[0]))) { com_err(argv[0], retval, gettext("while shutting down network")); errout++; } krb5_klog_syslog(LOG_INFO, "shutting down"); unload_preauth_plugins(kcontext); krb5_klog_close(kdc_context); finish_realms(argv[0]); if (kdc_realmlist) free(kdc_realmlist); #ifdef USE_RCACHE (void) krb5_rc_close(kcontext, kdc_rcache); #endif #ifndef NOCACHE kdc_free_lookaside(kcontext); #endif krb5_free_context(kcontext); return errout; } /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * kdc/network.c * * Copyright 1990,2000 by the Massachusetts Institute of Technology. * * Export of this software from the United States of America may * require a specific license from the United States Government. * It is the responsibility of any person or organization contemplating * export to obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of M.I.T. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. Furthermore if you modify this software you must label * your software as modified software and not distribute it in such a * fashion that it might be confused with the original M.I.T. software. * M.I.T. makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. * * * Network code for Kerberos v5 KDC. */ #include "k5-int.h" #include "com_err.h" #include "kdc_util.h" #include "extern.h" #include "kdc5_err.h" #include "adm_proto.h" #include #include #include #include #include "port-sockets.h" /* #include "socket-utils.h" */ #ifdef HAVE_NETINET_IN_H #include #include #include #ifdef HAVE_SYS_SOCKIO_H /* for SIOCGIFCONF, etc. */ #include #endif #include #include #if HAVE_SYS_SELECT_H #include #endif #include #include #include #ifndef ARPHRD_ETHER /* OpenBSD breaks on multiple inclusions */ #include #endif #ifdef HAVE_SYS_FILIO_H #include /* FIONBIO */ #endif #include "fake-addrinfo.h" /* Misc utility routines. */ static void set_sa_port(struct sockaddr *addr, int port) { switch (addr->sa_family) { case AF_INET: sa2sin(addr)->sin_port = port; break; #ifdef KRB5_USE_INET6 case AF_INET6: sa2sin6(addr)->sin6_port = port; break; #endif default: break; } } static int ipv6_enabled() { #ifdef KRB5_USE_INET6 static int result = -1; if (result == -1) { int s; s = socket(AF_INET6, SOCK_STREAM, 0); if (s >= 0) { result = 1; close(s); } else result = 0; } return result; #else return 0; #endif } static int setreuseaddr(int sock, int value) { return setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value)); } #if defined(KRB5_USE_INET6) && defined(IPV6_V6ONLY) static int setv6only(int sock, int value) { return setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &value, sizeof(value)); } #endif static const char *paddr (struct sockaddr *sa) { static char buf[100]; char portbuf[10]; if (getnameinfo(sa, socklen(sa), buf, sizeof(buf), portbuf, sizeof(portbuf), NI_NUMERICHOST|NI_NUMERICSERV)) strcpy(buf, ""); else { unsigned int len = sizeof(buf) - strlen(buf); char *p = buf + strlen(buf); if (len > 2+strlen(portbuf)) { *p++ = '.'; len--; strncpy(p, portbuf, len); } } return buf; } /* KDC data. */ enum kdc_conn_type { CONN_UDP, CONN_TCP_LISTENER, CONN_TCP }; /* Per-connection info. */ struct connection { int fd; enum kdc_conn_type type; void (*service)(struct connection *, const char *, int); /* Solaris Kerberos: for auditing */ in_port_t port; /* local port */ union { /* Type-specific information. */ struct { int x; } udp; struct { int x; } tcp_listener; struct { /* connection */ struct sockaddr_storage addr_s; socklen_t addrlen; char addrbuf[56]; krb5_fulladdr faddr; krb5_address kaddr; /* incoming */ size_t bufsiz; size_t offset; char *buffer; size_t msglen; /* outgoing */ krb5_data *response; unsigned char lenbuf[4]; sg_buf sgbuf[2]; sg_buf *sgp; int sgnum; /* crude denial-of-service avoidance support */ time_t start_time; } tcp; } u; }; #define SET(TYPE) struct { TYPE *data; int n, max; } /* Start at the top and work down -- this should allow for deletions without disrupting the iteration, since we delete by overwriting the element to be removed with the last element. */ #define FOREACH_ELT(set,idx,vvar) \ for (idx = set.n-1; idx >= 0 && (vvar = set.data[idx], 1); idx--) #define GROW_SET(set, incr, tmpptr) \ (((int)(set.max + incr) < set.max \ || (((size_t)((int)(set.max + incr) * sizeof(set.data[0])) \ / sizeof(set.data[0])) \ != (set.max + incr))) \ ? 0 /* overflow */ \ : ((tmpptr = realloc(set.data, \ (int)(set.max + incr) * sizeof(set.data[0]))) \ ? (set.data = tmpptr, set.max += incr, 1) \ : 0)) /* 1 = success, 0 = failure */ #define ADD(set, val, tmpptr) \ ((set.n < set.max || GROW_SET(set, 10, tmpptr)) \ ? (set.data[set.n++] = val, 1) \ : 0) #define DEL(set, idx) \ (set.data[idx] = set.data[--set.n]) #define FREE_SET_DATA(set) if(set.data) free(set.data); \ (set.data = 0, set.max = 0) /* Set connections; */ static SET(struct connection *) connections; #define n_sockets connections.n #define conns connections.data /* Set udp_port_data, tcp_port_data; */ static SET(u_short) udp_port_data, tcp_port_data; #include "cm.h" static struct select_state sstate; static krb5_error_code add_udp_port(int port) { int i; void *tmp; u_short val; u_short s_port = port; if (s_port != port) return EINVAL; FOREACH_ELT (udp_port_data, i, val) if (s_port == val) return 0; if (!ADD(udp_port_data, s_port, tmp)) return ENOMEM; return 0; } static krb5_error_code add_tcp_port(int port) { int i; void *tmp; u_short val; u_short s_port = port; if (s_port != port) return EINVAL; FOREACH_ELT (tcp_port_data, i, val) if (s_port == val) return 0; if (!ADD(tcp_port_data, s_port, tmp)) return ENOMEM; return 0; } #define USE_AF AF_INET #define USE_TYPE SOCK_DGRAM #define USE_PROTO 0 #define SOCKET_ERRNO errno #include "foreachaddr.h" struct socksetup { const char *prog; krb5_error_code retval; }; static struct connection * add_fd (struct socksetup *data, int sock, enum kdc_conn_type conntype, void (*service)(struct connection *, const char *, int)) { struct connection *newconn; void *tmp; newconn = malloc(sizeof(*newconn)); if (newconn == 0) { data->retval = errno; com_err(data->prog, errno, gettext("cannot allocate storage for connection info")); return 0; } if (!ADD(connections, newconn, tmp)) { data->retval = errno; com_err(data->prog, data->retval, gettext("cannot save socket info")); free(newconn); return 0; } memset(newconn, 0, sizeof(*newconn)); newconn->type = conntype; newconn->fd = sock; newconn->service = service; return newconn; } static void process_packet(struct connection *, const char *, int); static void accept_tcp_connection(struct connection *, const char *, int); static void process_tcp_connection(struct connection *, const char *, int); static struct connection * add_udp_fd (struct socksetup *data, int sock) { return add_fd(data, sock, CONN_UDP, process_packet); } static struct connection * add_tcp_listener_fd (struct socksetup *data, int sock) { return add_fd(data, sock, CONN_TCP_LISTENER, accept_tcp_connection); } static struct connection * add_tcp_data_fd (struct socksetup *data, int sock) { return add_fd(data, sock, CONN_TCP, process_tcp_connection); } static void delete_fd (struct connection *xconn) { struct connection *conn; int i; FOREACH_ELT(connections, i, conn) if (conn == xconn) { DEL(connections, i); break; } free(xconn); } static int setnbio(int sock) { static const int one = 1; return ioctlsocket(sock, FIONBIO, (const void *)&one); } static int setnolinger(int s) { static const struct linger ling = { 0, 0 }; return setsockopt(s, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling)); } /* Returns -1 or socket fd. */ static int setup_a_tcp_listener(struct socksetup *data, struct sockaddr *addr) { int sock; sock = socket(addr->sa_family, SOCK_STREAM, 0); if (sock == -1) { com_err(data->prog, errno, gettext("Cannot create TCP server socket on %s"), paddr(addr)); return -1; } /* * Solaris Kerberos: noticed that there where bind problems for tcp sockets * if kdc restarted quickly. Setting SO_REUSEADDR allowed binds to succeed. */ if (setreuseaddr(sock, 1) < 0) { com_err(data->prog, errno, gettext("enabling SO_REUSEADDR on TCP socket")); close(sock); return -1; } if (bind(sock, addr, socklen(addr)) == -1) { com_err(data->prog, errno, gettext("Cannot bind TCP server socket on %s"), paddr(addr)); close(sock); return -1; } if (listen(sock, 5) < 0) { com_err(data->prog, errno, gettext("Cannot listen on TCP server socket on %s"), paddr(addr)); close(sock); return -1; } if (setnbio(sock)) { com_err(data->prog, errno, gettext("cannot set listening tcp socket on %s non-blocking"), paddr(addr)); close(sock); return -1; } if (setnolinger(sock)) { com_err(data->prog, errno, gettext("disabling SO_LINGER on TCP socket on %s"), paddr(addr)); close(sock); return -1; } return sock; } static int setup_tcp_listener_ports(struct socksetup *data) { struct sockaddr_in sin4; #ifdef KRB5_USE_INET6 struct sockaddr_in6 sin6; #endif int i, port; memset(&sin4, 0, sizeof(sin4)); sin4.sin_family = AF_INET; #ifdef HAVE_SA_LEN sin4.sin_len = sizeof(sin4); #endif sin4.sin_addr.s_addr = INADDR_ANY; #ifdef KRB5_USE_INET6 memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; #ifdef SIN6_LEN sin6.sin6_len = sizeof(sin6); #endif sin6.sin6_addr = in6addr_any; #endif FOREACH_ELT (tcp_port_data, i, port) { int s4, s6; set_sa_port((struct sockaddr *)&sin4, htons(port)); if (!ipv6_enabled()) { s4 = setup_a_tcp_listener(data, (struct sockaddr *)&sin4); if (s4 < 0) return -1; s6 = -1; } else { #ifndef KRB5_USE_INET6 abort(); #else s4 = s6 = -1; set_sa_port((struct sockaddr *)&sin6, htons(port)); s6 = setup_a_tcp_listener(data, (struct sockaddr *)&sin6); if (s6 < 0) return -1; #ifdef IPV6_V6ONLY if (setv6only(s6, 0)) com_err(data->prog, errno, gettext("setsockopt(IPV6_V6ONLY,0) failed")); #endif s4 = setup_a_tcp_listener(data, (struct sockaddr *)&sin4); #endif /* KRB5_USE_INET6 */ } /* Sockets are created, prepare to listen on them. */ if (s4 >= 0) { FD_SET(s4, &sstate.rfds); if (s4 >= sstate.max) sstate.max = s4 + 1; if (add_tcp_listener_fd(data, s4) == 0) close(s4); else krb5_klog_syslog(LOG_INFO, "listening on fd %d: tcp %s", s4, paddr((struct sockaddr *)&sin4)); } #ifdef KRB5_USE_INET6 if (s6 >= 0) { FD_SET(s6, &sstate.rfds); if (s6 >= sstate.max) sstate.max = s6 + 1; if (add_tcp_listener_fd(data, s6) == 0) { close(s6); s6 = -1; } else krb5_klog_syslog(LOG_INFO, "listening on fd %d: tcp %s", s6, paddr((struct sockaddr *)&sin6)); if (s4 < 0) krb5_klog_syslog(LOG_INFO, "assuming IPv6 socket accepts IPv4"); } #endif } return 0; } static int setup_udp_port(void *P_data, struct sockaddr *addr) { struct socksetup *data = P_data; int sock = -1, i; char haddrbuf[NI_MAXHOST]; int err; u_short port; err = getnameinfo(addr, socklen(addr), haddrbuf, sizeof(haddrbuf), 0, 0, NI_NUMERICHOST); if (err) strcpy(haddrbuf, ""); switch (addr->sa_family) { case AF_INET: break; #ifdef AF_INET6 case AF_INET6: #ifdef KRB5_USE_INET6 break; #else { static int first = 1; if (first) { krb5_klog_syslog (LOG_INFO, "skipping local ipv6 addresses"); first = 0; } return 0; } #endif #endif #ifdef AF_LINK /* some BSD systems, AIX */ case AF_LINK: return 0; #endif #ifdef AF_DLI /* Direct Link Interface - DEC Ultrix/OSF1 link layer? */ case AF_DLI: return 0; #endif default: krb5_klog_syslog (LOG_INFO, "skipping unrecognized local address family %d", addr->sa_family); return 0; } FOREACH_ELT (udp_port_data, i, port) { sock = socket (addr->sa_family, SOCK_DGRAM, 0); if (sock == -1) { data->retval = errno; com_err(data->prog, data->retval, gettext("Cannot create server socket for port %d address %s"), port, haddrbuf); return 1; } set_sa_port(addr, htons(port)); if (bind (sock, (struct sockaddr *)addr, socklen (addr)) == -1) { data->retval = errno; com_err(data->prog, data->retval, gettext("Cannot bind server socket to port %d address %s"), port, haddrbuf); return 1; } FD_SET (sock, &sstate.rfds); if (sock >= sstate.max) sstate.max = sock + 1; krb5_klog_syslog (LOG_INFO, "listening on fd %d: udp %s", sock, paddr((struct sockaddr *)addr)); if (add_udp_fd (data, sock) == 0) return 1; } return 0; } #if 1 static void klog_handler(const void *data, size_t len) { static char buf[BUFSIZ]; static int bufoffset; void *p; #define flush_buf() \ (bufoffset \ ? (((buf[0] == 0 || buf[0] == '\n') \ ? (fork()==0?abort():(void)0) \ : (void)0), \ krb5_klog_syslog(LOG_INFO, "%s", buf), \ memset(buf, 0, sizeof(buf)), \ bufoffset = 0) \ : 0) p = memchr(data, 0, len); if (p) len = (const char *)p - (const char *)data; scan_for_newlines: if (len == 0) return; p = memchr(data, '\n', len); if (p) { if (p != data) klog_handler(data, (size_t)((const char *)p - (const char *)data)); flush_buf(); len -= ((const char *)p - (const char *)data) + 1; data = 1 + (const char *)p; goto scan_for_newlines; } else if (len > sizeof(buf) - 1 || len + bufoffset > sizeof(buf) - 1) { size_t x = sizeof(buf) - len - 1; klog_handler(data, x); flush_buf(); len -= x; data = (const char *)data + x; goto scan_for_newlines; } else { memcpy(buf + bufoffset, data, len); bufoffset += len; } } #endif /* XXX */ extern int krb5int_debug_sendto_kdc; extern void (*krb5int_sendtokdc_debug_handler)(const void*, size_t); krb5_error_code setup_network(const char *prog) { struct socksetup setup_data; krb5_error_code retval; char *cp; int i, port; FD_ZERO(&sstate.rfds); FD_ZERO(&sstate.wfds); FD_ZERO(&sstate.xfds); sstate.max = 0; /* krb5int_debug_sendto_kdc = 1; */ krb5int_sendtokdc_debug_handler = klog_handler; /* Handle each realm's ports */ for (i=0; irealm_ports; while (cp && *cp) { if (*cp == ',' || isspace((int) *cp)) { cp++; continue; } port = strtol(cp, &cp, 10); if (cp == 0) break; retval = add_udp_port(port); if (retval) return retval; } cp = kdc_realmlist[i]->realm_tcp_ports; while (cp && *cp) { if (*cp == ',' || isspace((int) *cp)) { cp++; continue; } port = strtol(cp, &cp, 10); if (cp == 0) break; retval = add_tcp_port(port); if (retval) return retval; } } setup_data.prog = prog; setup_data.retval = 0; krb5_klog_syslog (LOG_INFO, "setting up network..."); /* To do: Use RFC 2292 interface (or follow-on) and IPV6_PKTINFO, so we might need only one UDP socket; fall back to binding sockets on each address only if IPV6_PKTINFO isn't supported. */ if (foreach_localaddr (&setup_data, setup_udp_port, 0, 0)) { return setup_data.retval; } setup_tcp_listener_ports(&setup_data); krb5_klog_syslog (LOG_INFO, "set up %d sockets", n_sockets); if (n_sockets == 0) { com_err(prog, 0, gettext("no sockets set up?")); exit (1); } return 0; } static void init_addr(krb5_fulladdr *faddr, struct sockaddr *sa) { switch (sa->sa_family) { case AF_INET: faddr->address->addrtype = ADDRTYPE_INET; faddr->address->length = IPV4_ADDR_LEN; faddr->address->contents = (krb5_octet *) &sa2sin(sa)->sin_addr; faddr->port = ntohs(sa2sin(sa)->sin_port); break; #ifdef KRB5_USE_INET6 case AF_INET6: if (IN6_IS_ADDR_V4MAPPED(&sa2sin6(sa)->sin6_addr)) { faddr->address->addrtype = ADDRTYPE_INET; faddr->address->length = IPV4_ADDR_LEN; /* offset to RAM address of ipv4 part of ipv6 address */ faddr->address->contents = (IPV6_ADDR_LEN - IPV4_ADDR_LEN) + (krb5_octet *) &sa2sin6(sa)->sin6_addr; } else { faddr->address->addrtype = ADDRTYPE_INET6; faddr->address->length = IPV6_ADDR_LEN; faddr->address->contents = (krb5_octet *) &sa2sin6(sa)->sin6_addr; } faddr->port = ntohs(sa2sin6(sa)->sin6_port); break; #endif default: faddr->address->addrtype = -1; faddr->address->length = 0; faddr->address->contents = 0; faddr->port = 0; break; } } static void process_packet(struct connection *conn, const char *prog, int selflags) { int cc; socklen_t saddr_len; krb5_fulladdr faddr; krb5_error_code retval; struct sockaddr_storage saddr; krb5_address addr; krb5_data request; krb5_data *response; char pktbuf[MAX_DGRAM_SIZE]; int port_fd = conn->fd; response = NULL; saddr_len = sizeof(saddr); cc = recvfrom(port_fd, pktbuf, sizeof(pktbuf), 0, (struct sockaddr *)&saddr, &saddr_len); if (cc == -1) { if (errno != EINTR /* This is how Linux indicates that a previous transmission was refused, e.g., if the client timed out before getting the response packet. */ && errno != ECONNREFUSED ) com_err(prog, errno, gettext("while receiving from network")); return; } if (!cc) return; /* zero-length packet? */ request.length = cc; request.data = pktbuf; faddr.address = &addr; init_addr(&faddr, ss2sa(&saddr)); /* this address is in net order */ if ((retval = dispatch(&request, &faddr, &response))) { com_err(prog, retval, gettext("while dispatching (udp)")); return; } cc = sendto(port_fd, response->data, (socklen_t) response->length, 0, (struct sockaddr *)&saddr, saddr_len); if (cc == -1) { char addrbuf[46]; krb5_free_data(kdc_context, response); if (inet_ntop(((struct sockaddr *)&saddr)->sa_family, addr.contents, addrbuf, sizeof(addrbuf)) == 0) { strcpy(addrbuf, "?"); } com_err(prog, errno, gettext("while sending reply to %s/%d"), addrbuf, faddr.port); return; } if (cc != response->length) { krb5_free_data(kdc_context, response); com_err(prog, 0, gettext("short reply write %d vs %d\n"), response->length, cc); return; } krb5_free_data(kdc_context, response); return; } static int tcp_data_counter; /* Solaris kerberos: getting this value from elsewhere */ extern int max_tcp_data_connections; static void kill_tcp_connection(struct connection *); static void accept_tcp_connection(struct connection *conn, const char *prog, int selflags) { int s; struct sockaddr_storage addr_s; struct sockaddr *addr = (struct sockaddr *)&addr_s; socklen_t addrlen = sizeof(addr_s); struct socksetup sockdata; struct connection *newconn; char tmpbuf[10]; s = accept(conn->fd, addr, &addrlen); if (s < 0) return; setnbio(s), setnolinger(s); sockdata.prog = prog; sockdata.retval = 0; newconn = add_tcp_data_fd(&sockdata, s); if (newconn == 0) return; if (getnameinfo((struct sockaddr *)&addr_s, addrlen, newconn->u.tcp.addrbuf, sizeof(newconn->u.tcp.addrbuf), tmpbuf, sizeof(tmpbuf), NI_NUMERICHOST | NI_NUMERICSERV)) strcpy(newconn->u.tcp.addrbuf, "???"); else { char *p, *end; p = newconn->u.tcp.addrbuf; end = p + sizeof(newconn->u.tcp.addrbuf); p += strlen(p); if (end - p > 2 + strlen(tmpbuf)) { *p++ = '.'; strcpy(p, tmpbuf); } } #if 0 krb5_klog_syslog(LOG_INFO, "accepted TCP connection on socket %d from %s", s, newconn->u.tcp.addrbuf); #endif newconn->u.tcp.addr_s = addr_s; newconn->u.tcp.addrlen = addrlen; newconn->u.tcp.bufsiz = 1024 * 1024; newconn->u.tcp.buffer = malloc(newconn->u.tcp.bufsiz); newconn->u.tcp.start_time = time(0); if (++tcp_data_counter > max_tcp_data_connections) { struct connection *oldest_tcp = NULL; struct connection *c; int i; krb5_klog_syslog(LOG_INFO, "too many connections"); FOREACH_ELT (connections, i, c) { if (c->type != CONN_TCP) continue; if (c == newconn) continue; #if 0 krb5_klog_syslog(LOG_INFO, "fd %d started at %ld", c->fd, c->u.tcp.start_time); #endif if (oldest_tcp == NULL || oldest_tcp->u.tcp.start_time > c->u.tcp.start_time) oldest_tcp = c; } if (oldest_tcp != NULL) { krb5_klog_syslog(LOG_INFO, "dropping tcp fd %d from %s", oldest_tcp->fd, oldest_tcp->u.tcp.addrbuf); kill_tcp_connection(oldest_tcp); oldest_tcp = NULL; } } if (newconn->u.tcp.buffer == 0) { com_err(prog, errno, gettext("allocating buffer for new TCP session from %s"), newconn->u.tcp.addrbuf); delete_fd(newconn); close(s); tcp_data_counter--; return; } newconn->u.tcp.offset = 0; newconn->u.tcp.faddr.address = &newconn->u.tcp.kaddr; init_addr(&newconn->u.tcp.faddr, ss2sa(&newconn->u.tcp.addr_s)); SG_SET(&newconn->u.tcp.sgbuf[0], newconn->u.tcp.lenbuf, 4); SG_SET(&newconn->u.tcp.sgbuf[1], 0, 0); FD_SET(s, &sstate.rfds); if (sstate.max <= s) sstate.max = s + 1; } static void kill_tcp_connection(struct connection *conn) { if (conn->u.tcp.response) krb5_free_data(kdc_context, conn->u.tcp.response); if (conn->u.tcp.buffer) free(conn->u.tcp.buffer); FD_CLR(conn->fd, &sstate.rfds); FD_CLR(conn->fd, &sstate.wfds); if (sstate.max == conn->fd + 1) while (sstate.max > 0 && ! FD_ISSET(sstate.max-1, &sstate.rfds) && ! FD_ISSET(sstate.max-1, &sstate.wfds) /* && ! FD_ISSET(sstate.max-1, &sstate.xfds) */ ) sstate.max--; close(conn->fd); conn->fd = -1; delete_fd(conn); tcp_data_counter--; } static krb5_error_code make_toolong_error (krb5_data **out) { krb5_error errpkt; krb5_error_code retval; krb5_data *scratch; retval = krb5_us_timeofday(kdc_context, &errpkt.stime, &errpkt.susec); if (retval) return retval; errpkt.error = KRB_ERR_FIELD_TOOLONG; errpkt.server = tgs_server; errpkt.client = NULL; errpkt.cusec = 0; errpkt.ctime = 0; errpkt.text.length = 0; errpkt.text.data = 0; errpkt.e_data.length = 0; errpkt.e_data.data = 0; scratch = malloc(sizeof(*scratch)); if (scratch == NULL) return ENOMEM; retval = krb5_mk_error(kdc_context, &errpkt, scratch); if (retval) { free(scratch); return retval; } *out = scratch; return 0; } static void process_tcp_connection(struct connection *conn, const char *prog, int selflags) { if (selflags & SSF_WRITE) { ssize_t nwrote; SOCKET_WRITEV_TEMP tmp; nwrote = SOCKET_WRITEV(conn->fd, conn->u.tcp.sgp, conn->u.tcp.sgnum, tmp); if (nwrote < 0) { goto kill_tcp_connection; } if (nwrote == 0) /* eof */ goto kill_tcp_connection; while (nwrote) { sg_buf *sgp = conn->u.tcp.sgp; if (nwrote < SG_LEN(sgp)) { SG_ADVANCE(sgp, nwrote); nwrote = 0; } else { nwrote -= SG_LEN(sgp); conn->u.tcp.sgp++; conn->u.tcp.sgnum--; if (conn->u.tcp.sgnum == 0 && nwrote != 0) abort(); } } if (conn->u.tcp.sgnum == 0) { /* finished sending */ /* We should go back to reading, though if we sent a FIELD_TOOLONG error in reply to a length with the high bit set, RFC 4120 says we have to close the TCP stream. */ goto kill_tcp_connection; } } else if (selflags & SSF_READ) { /* Read message length and data into one big buffer, already allocated at connect time. If we have a complete message, we stop reading, so we should only be here if there is no data in the buffer, or only an incomplete message. */ size_t len; ssize_t nread; if (conn->u.tcp.offset < 4) { /* msglen has not been computed */ /* XXX Doing at least two reads here, letting the kernel worry about buffering. It'll be faster when we add code to manage the buffer here. */ len = 4 - conn->u.tcp.offset; nread = SOCKET_READ(conn->fd, conn->u.tcp.buffer + conn->u.tcp.offset, len); if (nread < 0) /* error */ goto kill_tcp_connection; if (nread == 0) /* eof */ goto kill_tcp_connection; conn->u.tcp.offset += nread; if (conn->u.tcp.offset == 4) { unsigned char *p = (unsigned char *)conn->u.tcp.buffer; conn->u.tcp.msglen = ((p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]); if (conn->u.tcp.msglen > conn->u.tcp.bufsiz - 4) { krb5_error_code err; /* message too big */ krb5_klog_syslog(LOG_ERR, "TCP client %s wants %lu bytes, cap is %lu", conn->u.tcp.addrbuf, (unsigned long) conn->u.tcp.msglen, (unsigned long) conn->u.tcp.bufsiz - 4); /* XXX Should return an error. */ err = make_toolong_error (&conn->u.tcp.response); if (err) { krb5_klog_syslog(LOG_ERR, "error constructing KRB_ERR_FIELD_TOOLONG error! %s", error_message(err)); goto kill_tcp_connection; } goto have_response; } } } else { /* msglen known */ krb5_data request; krb5_error_code err; len = conn->u.tcp.msglen - (conn->u.tcp.offset - 4); nread = SOCKET_READ(conn->fd, conn->u.tcp.buffer + conn->u.tcp.offset, len); if (nread < 0) /* error */ goto kill_tcp_connection; if (nread == 0) /* eof */ goto kill_tcp_connection; conn->u.tcp.offset += nread; if (conn->u.tcp.offset < conn->u.tcp.msglen + 4) return; /* have a complete message, and exactly one message */ request.length = conn->u.tcp.msglen; request.data = conn->u.tcp.buffer + 4; err = dispatch(&request, &conn->u.tcp.faddr, &conn->u.tcp.response); if (err) { com_err(prog, err, gettext("while dispatching (tcp)")); goto kill_tcp_connection; } have_response: conn->u.tcp.lenbuf[0] = 0xff & (conn->u.tcp.response->length >> 24); conn->u.tcp.lenbuf[1] = 0xff & (conn->u.tcp.response->length >> 16); conn->u.tcp.lenbuf[2] = 0xff & (conn->u.tcp.response->length >> 8); conn->u.tcp.lenbuf[3] = 0xff & (conn->u.tcp.response->length >> 0); SG_SET(&conn->u.tcp.sgbuf[1], conn->u.tcp.response->data, conn->u.tcp.response->length); conn->u.tcp.sgp = conn->u.tcp.sgbuf; conn->u.tcp.sgnum = 2; FD_CLR(conn->fd, &sstate.rfds); FD_SET(conn->fd, &sstate.wfds); } } else abort(); return; kill_tcp_connection: kill_tcp_connection(conn); } static void service_conn(struct connection *conn, const char *prog, int selflags) { conn->service(conn, prog, selflags); } krb5_error_code listen_and_process(const char *prog) { int nfound; /* This struct contains 3 fd_set objects; on some platforms, they can be rather large. Making this static avoids putting all that junk on the stack. */ static struct select_state sout; int i, sret; krb5_error_code err; if (conns == (struct connection **) NULL) return KDC5_NONET; while (!signal_requests_exit) { if (signal_requests_hup) { krb5_klog_reopen(kdc_context); signal_requests_hup = 0; } sstate.end_time.tv_sec = sstate.end_time.tv_usec = 0; err = krb5int_cm_call_select(&sstate, &sout, &sret); if (err) { com_err(prog, err, gettext("while selecting for network input(1)")); continue; } if (sret == -1) { if (errno != EINTR) com_err(prog, errno, gettext("while selecting for network input(2)")); continue; } nfound = sret; for (i=0; i 0; i++) { int sflags = 0; if (conns[i]->fd < 0) abort(); if (FD_ISSET(conns[i]->fd, &sout.rfds)) sflags |= SSF_READ, nfound--; if (FD_ISSET(conns[i]->fd, &sout.wfds)) sflags |= SSF_WRITE, nfound--; if (sflags) service_conn(conns[i], prog, sflags); } } return 0; } krb5_error_code closedown_network(const char *prog) { int i; struct connection *conn; if (conns == (struct connection **) NULL) return KDC5_NONET; FOREACH_ELT (connections, i, conn) { if (conn->fd >= 0) (void) close(conn->fd); DEL (connections, i); /* There may also be per-connection data in the tcp structure (tcp.buffer, tcp.response) that we're not freeing here. That should only happen if we quit with a connection in progress. */ free(conn); } FREE_SET_DATA(connections); FREE_SET_DATA(udp_port_data); FREE_SET_DATA(tcp_port_data); return 0; } #endif /* INET */ /* * kdc/policy.c * * Copyright 1990 by the Massachusetts Institute of Technology. * * Export of this software from the United States of America may * require a specific license from the United States Government. * It is the responsibility of any person or organization contemplating * export to obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of M.I.T. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. Furthermore if you modify this software you must label * your software as modified software and not distribute it in such a * fashion that it might be confused with the original M.I.T. software. * M.I.T. makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. * * * Policy decision routines for KDC. */ #include "k5-int.h" #include "kdc_util.h" int against_local_policy_as(register krb5_kdc_req *request, krb5_db_entry client, krb5_db_entry server, krb5_timestamp kdc_time, const char **status) { #if 0 /* An AS request must include the addresses field */ if (request->addresses == 0) { *status = "NO ADDRESS"; return KRB5KDC_ERR_POLICY; } #endif return 0; /* not against policy */ } /* * This is where local policy restrictions for the TGS should placed. */ krb5_error_code against_local_policy_tgs(register krb5_kdc_req *request, krb5_db_entry server, krb5_ticket *ticket, const char **status) { #if 0 /* * For example, if your site wants to disallow ticket forwarding, * you might do something like this: */ if (isflagset(request->kdc_options, KDC_OPT_FORWARDED)) { *status = "FORWARD POLICY"; return KRB5KDC_ERR_POLICY; } #endif return 0; /* not against policy */ } /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef __KRB5_KDC_POLICY__ #define __KRB5_KDC_POLICY__ #ifdef __cplusplus extern "C" { #endif /* * kdc/policy.h * * Copyright 1990 by the Massachusetts Institute of Technology. * * Export of this software from the United States of America may * require a specific license from the United States Government. * It is the responsibility of any person or organization contemplating * export to obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of M.I.T. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. Furthermore if you modify this software you must label * your software as modified software and not distribute it in such a * fashion that it might be confused with the original M.I.T. software. * M.I.T. makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. * * * Declarations for policy.c */ extern int against_postdate_policy (krb5_timestamp); extern int against_flag_policy_as (const krb5_kdc_req *); extern int against_flag_policy_tgs (const krb5_kdc_req *, const krb5_ticket *); #ifdef __cplusplus } #endif #endif /* !__KRB5_KDC_POLICY__ */ /* * kdc/replay.c * * Copyright 1991 by the Massachusetts Institute of Technology. * All Rights Reserved. * * Export of this software from the United States of America may * require a specific license from the United States Government. * It is the responsibility of any person or organization contemplating * export to obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of M.I.T. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. Furthermore if you modify this software you must label * your software as modified software and not distribute it in such a * fashion that it might be confused with the original M.I.T. software. * M.I.T. makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. * * * Replay lookaside cache for the KDC, to avoid extra work. * */ #include "k5-int.h" #include "kdc_util.h" #include "extern.h" #ifndef NOCACHE typedef struct _krb5_kdc_replay_ent { struct _krb5_kdc_replay_ent *next; int num_hits; krb5_int32 timein; time_t db_age; krb5_data *req_packet; krb5_data *reply_packet; } krb5_kdc_replay_ent; static krb5_kdc_replay_ent root_ptr = {0}; static int hits = 0; static int calls = 0; static int max_hits_per_entry = 0; static int num_entries = 0; #define STALE_TIME 2*60 /* two minutes */ #define STALE(ptr) ((abs((ptr)->timein - timenow) >= STALE_TIME) || \ ((ptr)->db_age != db_age)) #define MATCH(ptr) (((ptr)->req_packet->length == inpkt->length) && \ !memcmp((ptr)->req_packet->data, inpkt->data, \ inpkt->length) && \ ((ptr)->db_age == db_age)) /* XXX Todo: quench the size of the queue... */ /* return TRUE if outpkt is filled in with a packet to reply with, FALSE if the caller should do the work */ krb5_boolean kdc_check_lookaside(krb5_data *inpkt, krb5_data **outpkt) { krb5_int32 timenow; register krb5_kdc_replay_ent *eptr, *last, *hold; time_t db_age; if (krb5_timeofday(kdc_context, &timenow) || krb5_db_get_age(kdc_context, 0, &db_age)) return FALSE; calls++; /* search for a replay entry in the queue, possibly removing stale entries while we're here */ if (root_ptr.next) { for (last = &root_ptr, eptr = root_ptr.next; eptr; eptr = eptr->next) { if (MATCH(eptr)) { eptr->num_hits++; hits++; if (krb5_copy_data(kdc_context, eptr->reply_packet, outpkt)) return FALSE; else return TRUE; /* return here, don't bother flushing even if it is stale. if we just matched, we may get another retransmit... */ } if (STALE(eptr)) { /* flush it and collect stats */ max_hits_per_entry = max(max_hits_per_entry, eptr->num_hits); krb5_free_data(kdc_context, eptr->req_packet); krb5_free_data(kdc_context, eptr->reply_packet); hold = eptr; last->next = eptr->next; eptr = last; free(hold); } else { /* this isn't it, just move along */ last = eptr; } } } return FALSE; } /* insert a request & reply into the lookaside queue. assumes it's not already there, and can fail softly due to other weird errors. */ void kdc_insert_lookaside(krb5_data *inpkt, krb5_data *outpkt) { register krb5_kdc_replay_ent *eptr; krb5_int32 timenow; time_t db_age; if (krb5_timeofday(kdc_context, &timenow) || krb5_db_get_age(kdc_context, 0, &db_age)) return; /* this is a new entry */ eptr = (krb5_kdc_replay_ent *)calloc(1, sizeof(*eptr)); if (!eptr) return; eptr->timein = timenow; eptr->db_age = db_age; /* * This is going to hurt a lot malloc()-wise due to the need to * allocate memory for the krb5_data and krb5_address elements. * ARGH! */ if (krb5_copy_data(kdc_context, inpkt, &eptr->req_packet)) { free(eptr); return; } if (krb5_copy_data(kdc_context, outpkt, &eptr->reply_packet)) { krb5_free_data(kdc_context, eptr->req_packet); free(eptr); return; } eptr->next = root_ptr.next; root_ptr.next = eptr; num_entries++; return; } /* frees memory associated with the lookaside queue for memory profiling */ void kdc_free_lookaside(krb5_context kcontext) { register krb5_kdc_replay_ent *eptr, *last, *hold; if (root_ptr.next) { for (last = &root_ptr, eptr = root_ptr.next; eptr; eptr = eptr->next) { krb5_free_data(kcontext, eptr->req_packet); krb5_free_data(kcontext, eptr->reply_packet); hold = eptr; last->next = eptr->next; eptr = last; free(hold); } } } #endif /* NOCACHE */ /* * kdc/sock2p.c * * Copyright 2000 by the Massachusetts Institute of Technology. * * Export of this software from the United States of America may * require a specific license from the United States Government. * It is the responsibility of any person or organization contemplating * export to obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of M.I.T. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. Furthermore if you modify this software you must label * your software as modified software and not distribute it in such a * fashion that it might be confused with the original M.I.T. software. * M.I.T. makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. * * * Network code for Kerberos v5 KDC. */ #define NEED_SOCKETS #include "k5-int.h" #ifdef HAVE_NETINET_IN_H #include #include #include #ifndef HAVE_INET_NTOP char * inet_ntop (int family, const void *address, char *buf, size_t bufsiz) { char *p; switch (family) { case AF_INET: { p = inet_ntoa (*(const struct in_addr *)address); try: if (strlen (p) >= bufsiz) return 0; strcpy (buf, p); break; } #ifdef KRB5_USE_INET6 case AF_INET6: { char abuf[46]; const unsigned char *byte = (const unsigned char *) &((const struct in6_addr *)address)->s6_addr; sprintf (abuf, "%x:%x:%x:%x:%x:%x:%x:%x", byte[0] * 256 + byte[1], byte[2] * 256 + byte[3], byte[4] * 256 + byte[5], byte[6] * 256 + byte[7], byte[8] * 256 + byte[9], byte[10] * 256 + byte[11], byte[12] * 256 + byte[13], byte[14] * 256 + byte[15]); p = abuf; goto try; } #endif /* KRB5_USE_INET6 */ default: return 0; } return buf; } #endif void sockaddr2p (const struct sockaddr *s, char *buf, size_t bufsiz, int *port_p) { const void *addr; int port; switch (s->sa_family) { case AF_INET: addr = &((const struct sockaddr_in *)s)->sin_addr; port = ((const struct sockaddr_in *)s)->sin_port; break; #ifdef KRB5_USE_INET6 case AF_INET6: addr = &((const struct sockaddr_in6 *)s)->sin6_addr; port = ((const struct sockaddr_in6 *)s)->sin6_port; break; #endif default: if (bufsiz >= 2) strcpy (buf, "?"); if (port_p) *port_p = -1; return; } if (inet_ntop (s->sa_family, addr, buf, bufsiz) == 0 && bufsiz >= 2) strcpy (buf, "?"); if (port_p) *port_p = port; } #endif /* INET */