/* * lib/krb4/g_ad_tkt.c * * Copyright 1986, 1987, 1988, 2000, 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. */ #include "krb.h" #include "des.h" #include "krb4int.h" #include "prot.h" #include #include extern int krb_debug; extern int swap_bytes; /* * get_ad_tkt obtains a new service ticket from Kerberos, using * the ticket-granting ticket which must be in the ticket file. * It is typically called by krb_mk_req() when the client side * of an application is creating authentication information to be * sent to the server side. * * get_ad_tkt takes four arguments: three pointers to strings which * contain the name, instance, and realm of the service for which the * ticket is to be obtained; and an integer indicating the desired * lifetime of the ticket. * * It returns an error status if the ticket couldn't be obtained, * or AD_OK if all went well. The ticket is stored in the ticket * cache. * * The request sent to the Kerberos ticket-granting service looks * like this: * * pkt->dat * * TEXT original contents of authenticator+ticket * pkt->dat built in krb_mk_req call * * 4 bytes time_ws always 0 (?) FIXME! * char lifetime lifetime argument passed * string service service name argument * string sinstance service instance arg. * * See "prot.h" for the reply packet layout and definitions of the * extraction macros like pkt_version(), pkt_msg_type(), etc. */ /* * g_ad_tk_parse() * * Parse the returned packet from the KDC. * * Note that the caller is responsible for clearing the returned * session key if there is an error; that makes the error handling * code a little less hairy. */ static int g_ad_tkt_parse(KTEXT rpkt, C_Block tgtses, C_Block ses, char *s_name, char *s_instance, char *rlm, char *service, char *sinstance, char *realm, int *lifetime, int *kvno, KTEXT tkt, unsigned KRB4_32 *kdc_time, KRB4_32 *t_local) { unsigned char *ptr; unsigned int t_switch; int msg_byte_order; unsigned long rep_err_code; unsigned long cip_len; KTEXT_ST cip_st; KTEXT cip = &cip_st; /* Returned Ciphertext */ Key_schedule key_s; int len, i; KRB4_32 t_diff; /* Difference between timestamps */ ptr = rpkt->dat; #define RPKT_REMAIN (rpkt->length - (ptr - rpkt->dat)) if (RPKT_REMAIN < 1 + 1) return INTK_PROT; /* check packet version of the returned packet */ if (*ptr++ != KRB_PROT_VERSION) return INTK_PROT; /* This used to be switch (pkt_msg_type(rpkt) & ~1) { but SCO 3.2v4 cc compiled that incorrectly. */ t_switch = *ptr++; /* Check byte order (little-endian == 1) */ msg_byte_order = t_switch & 1; t_switch &= ~1; /* * Skip over some stuff (3 strings and various integers -- see * cr_auth_repl.c for details). Maybe we should actually verify * these? */ for (i = 0; i < 3; i++) { len = krb4int_strnlen((char *)ptr, RPKT_REMAIN) + 1; if (len <= 0) return INTK_PROT; ptr += len; } switch (t_switch) { case AUTH_MSG_KDC_REPLY: if (RPKT_REMAIN < 4 + 1 + 4 + 1) return INTK_PROT; ptr += 4 + 1 + 4 + 1; break; case AUTH_MSG_ERR_REPLY: if (RPKT_REMAIN < 8) return INTK_PROT; ptr += 4; KRB4_GET32(rep_err_code, ptr, msg_byte_order); return rep_err_code; default: return INTK_PROT; } /* Extract the ciphertext */ if (RPKT_REMAIN < 2) return INTK_PROT; KRB4_GET16(cip_len, ptr, msg_byte_order); if (RPKT_REMAIN < cip_len) return INTK_PROT; /* * RPKT_REMAIN will always be non-negative and at most the maximum * possible value of cip->length, so this assignment is safe. */ cip->length = cip_len; memcpy(cip->dat, ptr, (size_t)cip->length); ptr += cip->length; #ifndef NOENCRYPTION /* Attempt to decrypt it */ key_sched(tgtses, key_s); DEB (("About to do decryption ...")); pcbc_encrypt((C_Block *)cip->dat, (C_Block *)cip->dat, (long)cip->length, key_s, (C_Block *)tgtses, 0); #endif /* !NOENCRYPTION */ /* * Stomp on key schedule. Caller should stomp on tgtses. */ memset(key_s, 0, sizeof(key_s)); ptr = cip->dat; #define CIP_REMAIN (cip->length - (ptr - cip->dat)) if (CIP_REMAIN < 8) return RD_AP_MODIFIED; memcpy(ses, ptr, 8); /* * Stomp on decrypted session key immediately after copying it. */ memset(ptr, 0, 8); ptr += 8; len = krb4int_strnlen((char *)ptr, CIP_REMAIN) + 1; if (len <= 0 || len > SNAME_SZ) return RD_AP_MODIFIED; memcpy(s_name, ptr, (size_t)len); ptr += len; len = krb4int_strnlen((char *)ptr, CIP_REMAIN) + 1; if (len <= 0 || len > INST_SZ) return RD_AP_MODIFIED; memcpy(s_instance, ptr, (size_t)len); ptr += len; len = krb4int_strnlen((char *)ptr, CIP_REMAIN) + 1; if (len <= 0 || len > REALM_SZ) return RD_AP_MODIFIED; memcpy(rlm, ptr, (size_t)len); ptr += len; if (strcmp(s_name, service) || strcmp(s_instance, sinstance) || strcmp(rlm, realm)) /* not what we asked for */ return INTK_ERR; /* we need a better code here XXX */ if (CIP_REMAIN < 1 + 1 + 1) return RD_AP_MODIFIED; *lifetime = *ptr++; *kvno = *ptr++; tkt->length = *ptr++; if (CIP_REMAIN < tkt->length) return RD_AP_MODIFIED; memcpy(tkt->dat, ptr, (size_t)tkt->length); ptr += tkt->length; /* Time (coarse) */ if (CIP_REMAIN < 4) return RD_AP_MODIFIED; KRB4_GET32(*kdc_time, ptr, msg_byte_order); /* check KDC time stamp */ *t_local = TIME_GMT_UNIXSEC; t_diff = *t_local - *kdc_time; if (t_diff < 0) t_diff = -t_diff; /* Absolute value of difference */ if (t_diff > CLOCK_SKEW) return RD_AP_TIME; /* XXX should probably be better code */ return 0; } int KRB5_CALLCONV get_ad_tkt(service, sinstance, realm, lifetime) char *service; char *sinstance; char *realm; int lifetime; { KTEXT_ST pkt_st; KTEXT pkt = & pkt_st; /* Packet to KDC */ KTEXT_ST rpkt_st; KTEXT rpkt = &rpkt_st; /* Returned packet */ KTEXT_ST tkt_st; KTEXT tkt = &tkt_st; /* Current ticket */ C_Block ses; /* Session key for tkt */ CREDENTIALS cr; int kvno; /* Kvno for session key */ int kerror; char lrealm[REALM_SZ]; KRB4_32 time_ws = 0; char s_name[SNAME_SZ]; char s_instance[INST_SZ]; char rlm[REALM_SZ]; unsigned char *ptr; KRB4_32 t_local; struct sockaddr_in laddr; socklen_t addrlen; unsigned KRB4_32 kdc_time; /* KDC time */ size_t snamelen, sinstlen; kerror = krb_get_tf_realm(TKT_FILE, lrealm); #if USE_LOGIN_LIBRARY if (kerror == GC_NOTKT) { /* No tickets... call krb_get_cred (KLL will prompt) and try again. */ if ((kerror = krb_get_cred ("krbtgt", realm, realm, &cr)) == KSUCCESS) { /* Now get the realm again. */ kerror = krb_get_tf_realm (TKT_FILE, lrealm); } } #endif if (kerror != KSUCCESS) return kerror; /* Create skeleton of packet to be sent */ pkt->length = 0; /* * Look for the session key (and other stuff we don't need) * in the ticket file for krbtgt.realm@lrealm where "realm" * is the service's realm (passed in "realm" argument) and * "lrealm" is the realm of our initial ticket (the local realm). * If that fails, and the server's realm and the local realm are * the same thing, give up - no TGT available for local realm. * * If the server realm and local realm are different, though, * try getting a ticket-granting ticket for the server's realm, * i.e. a ticket for "krbtgt.alienrealm@lrealm", by calling get_ad_tkt(). * If that succeeds, the ticket will be in ticket cache, get it * into the "cr" structure by calling krb_get_cred(). */ kerror = krb_get_cred("krbtgt", realm, lrealm, &cr); if (kerror != KSUCCESS) { /* * If realm == lrealm, we have no hope, so let's not even try. */ if (strncmp(realm, lrealm, sizeof(lrealm)) == 0) return AD_NOTGT; else { kerror = get_ad_tkt("krbtgt", realm, lrealm, lifetime); if (kerror != KSUCCESS) { if (kerror == KDC_PR_UNKNOWN) /* no cross-realm ticket */ return AD_NOTGT; /* So call it no ticket */ return kerror; } kerror = krb_get_cred("krbtgt",realm,lrealm,&cr); if (kerror != KSUCCESS) return kerror; } } /* * Make up a request packet to the "krbtgt.realm@lrealm". * Start by calling krb_mk_req() which puts ticket+authenticator * into "pkt". Then tack other stuff on the end. */ kerror = krb_mk_req(pkt, "krbtgt", realm, lrealm, 0L); if (kerror) { /* stomp stomp stomp */ memset(cr.session, 0, sizeof(cr.session)); return AD_NOTGT; } ptr = pkt->dat + pkt->length; snamelen = strlen(service) + 1; sinstlen = strlen(sinstance) + 1; if (sizeof(pkt->dat) - (ptr - pkt->dat) < (4 + 1 + snamelen + sinstlen)) { /* stomp stomp stomp */ memset(cr.session, 0, sizeof(cr.session)); return INTK_ERR; } /* timestamp */ /* FIXME -- always 0 now, should we fill it in??? */ KRB4_PUT32BE(ptr, time_ws); *ptr++ = lifetime; memcpy(ptr, service, snamelen); ptr += snamelen; memcpy(ptr, sinstance, sinstlen); ptr += sinstlen; pkt->length = ptr - pkt->dat; /* Send the request to the local ticket-granting server */ rpkt->length = 0; addrlen = sizeof(laddr); kerror = krb4int_send_to_kdc_addr(pkt, rpkt, realm, (struct sockaddr *)&laddr, &addrlen); if (!kerror) { /* No error; parse return packet from KDC. */ kerror = g_ad_tkt_parse(rpkt, cr.session, ses, s_name, s_instance, rlm, service, sinstance, realm, &lifetime, &kvno, tkt, &kdc_time, &t_local); } /* * Unconditionally stomp on cr.session because we don't need it * anymore. */ memset(cr.session, 0, sizeof(cr.session)); if (kerror) { /* * Stomp on ses for good measure, since g_ad_tkt_parse() * doesn't do that for us. */ memset(ses, 0, sizeof(ses)); return kerror; } kerror = krb4int_save_credentials_addr(s_name, s_instance, rlm, ses, lifetime, kvno, tkt, t_local, laddr.sin_addr.s_addr); /* * Unconditionally stomp on ses because we don't need it anymore. */ memset(ses, 0, sizeof(ses)); if (kerror) return kerror; return AD_OK; }