From 67c751774d700487072bf7539ff15f153566ee3a Mon Sep 17 00:00:00 2001 From: Ken Hornstein Date: Thu, 6 Feb 2003 19:58:18 +0000 Subject: [PATCH] This is the integration of "fakeka" (a program to emulate a kaserver) into the MIT distribution. It's compilation is enabled with --enable-fakeka. ticket: 1281 git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@15158 dc483132-0cff-0310-8789-dd5450dbe970 --- src/kdc/Makefile.in | 10 +- src/kdc/configure.in | 10 + src/kdc/fakeka.c | 1392 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1411 insertions(+), 1 deletion(-) create mode 100644 src/kdc/fakeka.c diff --git a/src/kdc/Makefile.in b/src/kdc/Makefile.in index 63eca304b..368dfaf91 100644 --- a/src/kdc/Makefile.in +++ b/src/kdc/Makefile.in @@ -11,8 +11,9 @@ DEFINES = # -DNOCACHE RUN_SETUP = @KRB5_RUN_ENV@ PROG_LIBPATH=-L$(TOPLIBD) $(KRB4_LIBPATH) PROG_RPATH=$(KRB5_LIBDIR) +FAKEKA=@FAKEKA@ -all:: krb5kdc rtest +all:: krb5kdc rtest $(FAKEKA) # DEFINES = -DBACKWARD_COMPAT $(KRB4DEF) @@ -72,6 +73,9 @@ krb5kdc: $(OBJS) $(KADMSRV_DEPLIBS) $(KRB4COMPAT_DEPLIBS) rtest: $(RT_OBJS) $(KDB5_DEPLIBS) $(KADM_COMM_DEPLIBS) $(KRB5_BASE_DEPLIBS) $(CC_LINK) -o rtest $(RT_OBJS) $(KDB5_LIBS) $(KADM_COMM_LIBS) $(KRB5_BASE_LIBS) +fakeka: fakeka.o $(KADMSRV_DEPLIBS) $(KRB4COMPAT_DEPLIBS) + $(CC_LINK) -o fakeka fakeka.o $(KADMSRV_LIBS) $(KRB4COMPAT_LIBS) + check-unix:: rtest KRB5_CONFIG=$(SRCTOP)/config-files/krb5.conf ; export KRB5_CONFIG ;\ $(RUN_SETUP) $(srcdir)/rtscript > test.out @@ -81,6 +85,10 @@ check-unix:: rtest install:: $(INSTALL_PROGRAM) krb5kdc ${DESTDIR}$(SERVER_BINDIR)/krb5kdc $(INSTALL_DATA) $(srcdir)/krb5kdc.M ${DESTDIR}$(SERVER_MANDIR)/krb5kdc.8 + f=$(FAKEKA); \ + if test -n "$$f" ; then \ + $(INSTALL_PROGRAM) $$f ${DESTDIR}$(SERVER_BINDIR)/$$f; \ + fi clean:: $(RM) kdc5_err.h kdc5_err.c krb5kdc logger.c rtest.o rtest diff --git a/src/kdc/configure.in b/src/kdc/configure.in index 305c2fe85..45b9a22b5 100644 --- a/src/kdc/configure.in +++ b/src/kdc/configure.in @@ -73,6 +73,16 @@ if test "$enableval" = yes ; then else AC_DEFINE(NOCACHE) fi +AC_ARG_ENABLE([fakeka], +[ --enable-fakeka Enable the build of the Fake KA server + (emulates an AFS (kaserver) + --disable-fakeka Do not build the fakeka server (default)])dnl +if test "$enableval" = yes; then + FAKEKA=fakeka +else + FAKEKA= +fi +AC_SUBST(FAKEKA) dnl dnl KRB5_RUN_FLAGS diff --git a/src/kdc/fakeka.c b/src/kdc/fakeka.c new file mode 100644 index 000000000..040e88845 --- /dev/null +++ b/src/kdc/fakeka.c @@ -0,0 +1,1392 @@ +/* + * COPYRIGHT NOTICE + * Copyright (c) 1994 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software_Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie Mellon + * the rights to redistribute these changes. + * + * Converted to Kerberos 5 by Ken Hornstein + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_MEMORY_H +#include +#endif + +#include +#include +#include +#include +#include + +#ifndef LINT +static char rcsid[]= + "$Id$"; +#endif + +/* + * Misc macros + */ + +#define PAD_TO(x, a) (((u_long)(x) + (a) - 1) & ~((a) - 1)) +#define min(a, b) ((a) < (b) ? (a) : (b)) +#define MAXFORWARDERS 10 +#define HEADER_LEN 8 + +/* + * Error values from kautils.h + * + * The security errors are: + * KABADTICKET, KABADSERVER, KABADUSER, and KACLOCKSKEW + */ + +#define KADATABASEINCONSISTENT (180480L) +#define KANOENT (180484L) +#define KABADREQUEST (180490L) +#define KABADTICKET (180504L) +#define KABADSERVER (180507L) +#define KABADUSER (180508L) +#define KACLOCKSKEW (180514L) +#define KAINTERNALERROR (180518L) + + +/* + * Type definitions + */ + +typedef struct packet { + char *base; + int len; + char data[1024]; +} *packet_t; + +typedef struct rx_header { + u_int rx_epoch; + u_int rx_cid; + u_int rx_callnum; + u_int rx_seq; + u_int rx_serial; + u_char rx_type; + u_char rx_flags; + u_char rx_userstatus; + u_char rx_securityindex; + u_short rx_spare; + u_short rx_service; + u_int rx_request; +} *rx_t; + + +/* + * Global vars + */ + +char *progname = "fakeka"; /* needed by libkdb.a */ +char *localrealm = NULL; +char *localcell = NULL; +krb5_timestamp req_time; +kadm5_config_params realm_params; +int debug = 0; + + +/* + * This is a table for the "infamous" CMU ticket lifetime conversion. If + * the lifetime is greater than 128, use this table + */ +#define MAX_TICKET_LIFETIME 2592000 +static long cmu_seconds[] = +{ + 38400, 41055, 43894, 46929, 50174, 53643, 57352, 61318, + 65558, 70091, 74937, 80119, 85658, 91581, 97914, 104684, + 111922, 119661, 127935, 136781, 146239, 156350, 167161, 178720, + 191077, 204289, 218415, 233517, 249663, 266926, 285383, 305116, + 326213, 348769, 372885, 398668, 426233, 455705, 487215, 520903, + 556921, 595430, 636600, 680618, 727679, 777995, 831789, 889303, + 950794, 1016536, 1086825, 1161973, 1242317, 1328217, 1420057, 1518246, + 1623225, 1735463, 1855462, 1983757, 2120924, 2267575, 2424366, 2591999, + 0 +}; + +#if __STDC__ +/* + * Prototypes for all the functions we define + */ + +void perrorexit(char *); +void pexit(char *); +char *kaerror(int); +int get_princ_key(krb5_context, void *, kadm5_principal_ent_t, des_cblock, + des_key_schedule); +int check_princ(krb5_context, void *, char *, char *, kadm5_principal_ent_t); + +int make_reply_packet(krb5_context, void *, packet_t, int, int, int, + char *, char *, char *, char *, + des_cblock, des_key_schedule, char *); + +int Authenticate(krb5_context, void *, char *, packet_t, packet_t); +int GetTicket(krb5_context, void *, char *, packet_t, packet_t); +void process(krb5_context, void *, char *, packet_t, packet_t); +#endif + + +/* + * Helpers for exiting with errors + */ + +void perrorexit(str) +char *str; +{ + perror(str); + exit(1); +} + +void pexit(str) +char *str; +{ + printf("%s\n", str); + exit(1); +} + + +/* + * Translate error codes into strings. + */ + +char *kaerror(e) +int e; +{ + static char buf[1024]; + + switch (e) { + case KADATABASEINCONSISTENT: + return "database is inconsistent"; + case KANOENT: + return "principal does not exist"; + case KABADREQUEST: + return "request was malformed (bad password)"; + case KABADTICKET: + return "ticket was malformed, invalid, or expired"; + case KABADSERVER: + return "cannot issue tickets for this service"; + case KABADUSER: + return "principal expired"; + case KACLOCKSKEW: + return "client time is too far skewed"; + case KAINTERNALERROR: + return "internal error in fakeka, help!"; + default: + sprintf(buf, "impossible error code %d, help!", e); + return buf; + } + /*NOTREACHED*/ +} + +/* + * Syslog facilities + */ +typedef struct { + int num; + char *string; +} facility_mapping; + +static facility_mapping mappings[] = { +#ifdef LOG_KERN + { LOG_KERN, "KERN" }, +#endif +#ifdef LOG_USER + { LOG_USER, "USER" }, +#endif +#ifdef LOG_MAIL + { LOG_MAIL, "MAIL" }, +#endif +#ifdef LOG_DAEMON + { LOG_DAEMON, "DAEMON" }, +#endif +#ifdef LOG_AUTH + { LOG_AUTH, "AUTH" }, +#endif +#ifdef LOG_LPR + { LOG_LPR, "LPR" }, +#endif +#ifdef LOG_NEWS + { LOG_NEWS, "NEWS" }, +#endif +#ifdef LOG_UUCP + { LOG_UUCP, "UUCP" }, +#endif +#ifdef LOG_CRON + { LOG_CRON, "CRON" }, +#endif +#ifdef LOG_LOCAL0 + { LOG_LOCAL0, "LOCAL0" }, +#endif +#ifdef LOG_LOCAL1 + { LOG_LOCAL1, "LOCAL1" }, +#endif +#ifdef LOG_LOCAL2 + { LOG_LOCAL2, "LOCAL2" }, +#endif +#ifdef LOG_LOCAL3 + { LOG_LOCAL3, "LOCAL3" }, +#endif +#ifdef LOG_LOCAL4 + { LOG_LOCAL4, "LOCAL4" }, +#endif +#ifdef LOG_LOCAL5 + { LOG_LOCAL5, "LOCAL5" }, +#endif +#ifdef LOG_LOCAL6 + { LOG_LOCAL6, "LOCAL6" }, +#endif +#ifdef LOG_LOCAL7 + { LOG_LOCAL7, "LOCAL7" }, +#endif + { 0, NULL } +}; + + +/* + * Get the principal's key and key schedule from the db record. + * + * Life is more complicated in the V5 world. Since we can have different + * encryption types, we have to make sure that we get back a DES key. + * Also, we have to try to get back a AFS3 or V4 salted key, since AFS + * doesn't know about a V5 style salt. + */ + +int get_princ_key(context, handle, p, k, s) +krb5_context context; +void *handle; +kadm5_principal_ent_t p; +des_cblock k; +des_key_schedule s; +{ + int rv; + krb5_keyblock kb; + kadm5_ret_t retval; + + /* + * We need to call kadm5_decrypt_key to decrypt the key data + * from the principal record. We _must_ have a encryption type + * of DES_CBC_CRC, and we prefer having a salt type of AFS 3 (but + * a V4 salt will work as well). If that fails, then return any + * type of key we can find. + * + * Note that since this uses kadm5_decrypt_key, it means it has to + * be compiled with the kadm5srv library. + */ + + if ((retval = kadm5_decrypt_key(handle, p, ENCTYPE_DES_CBC_CRC, + KRB5_KDB_SALTTYPE_AFS3, 0, &kb, + NULL, NULL))) + if ((retval = kadm5_decrypt_key(handle, p, ENCTYPE_DES_CBC_CRC, + KRB5_KDB_SALTTYPE_V4, 0, &kb, + NULL, NULL))) + if ((retval = kadm5_decrypt_key(handle, p, ENCTYPE_DES_CBC_CRC, + -1, 0, &kb, NULL, NULL))) { + syslog(LOG_ERR, "Couldn't find any matching key: %s", + error_message(retval)); + return KAINTERNALERROR; + } + + /* + * Copy the data from our krb5_keyblock to the des_cblock. Make sure + * the size of our key matches the V4/AFS des_cblock. + */ + + if (kb.length != sizeof(des_cblock)) { + krb5_free_keyblock_contents(context, &kb); + syslog(LOG_ERR, "Principal key size of %d didn't match C_Block size" + " %d", kb.length, sizeof(des_cblock)); + return KAINTERNALERROR; + } + + memcpy((char *) k, (char *) kb.contents, sizeof(des_cblock)); + + krb5_free_keyblock_contents(context, &kb); + + /* + * Calculate the des key schedule + */ + + rv = des_key_sched(k, s); + if (rv) { + memset((void *) k, 0, sizeof(k)); + memset((void *)s, 0, sizeof(s)); + return KAINTERNALERROR; + } + return 0; +} + + +/* + * Fetch principal from db and validate it. + * + * Note that this always fetches the key data from the principal (but it + * doesn't decrypt it). + */ + +int check_princ(context, handle, name, inst, p) +krb5_context context; +void *handle; +char *name, *inst; +kadm5_principal_ent_t p; +{ + krb5_principal princ; + krb5_error_code code; + kadm5_ret_t retcode; + + /* + * Screen out null principals. They are causing crashes here + * under HPUX-10.20. - vwelch@ncsa.uiuc.edu 1/6/98 + */ + if (!name || (name[0] == '\0')) { + syslog(LOG_ERR, "screening out null principal"); + return KANOENT; + } + + /* + * Build a principal from the name and instance (the realm is always + * the same). + */ + + if ((code = krb5_build_principal_ext(context, &princ, strlen(localrealm), + localrealm, strlen(name), name, + strlen(inst), inst, 0))) { + syslog(LOG_ERR, "could not build principal: %s", error_message(code)); + return KAINTERNALERROR; + } + + /* + * Fetch the principal from the database -- also fetch the key data. + * Note that since this retrieves the key data, it has to be linked with + * the kadm5srv library. + */ + + if ((retcode = kadm5_get_principal(handle, princ, p, + KADM5_PRINCIPAL_NORMAL_MASK | + KADM5_KEY_DATA))) { + if (retcode == KADM5_UNK_PRINC) { + krb5_free_principal(context, princ); + syslog(LOG_INFO, "principal %s.%s does not exist", name, inst); + return KANOENT; + } else { + krb5_free_principal(context, princ); + syslog(LOG_ERR, "kadm5_get_principal failed: %s", + error_message(retcode)); + return KAINTERNALERROR; + } + } + + krb5_free_principal(context, princ); + + /* + * Check various things - taken from the KDC code. + * + * Since we're essentially bypassing the KDC, we need to make sure + * that we don't give out a ticket that we shouldn't. + */ + + /* + * Has the principal expired? + */ + + if (p->princ_expire_time && p->princ_expire_time < req_time) { + kadm5_free_principal_ent(handle, p); + return KABADUSER; + } + + /* + * Has the principal's password expired? Note that we don't + * check for the PWCHANGE_SERVICE flag here, since we don't + * support password changing. We do support the REQUIRES_PWCHANGE + * flag, though. + */ + + if ((p->pw_expiration && p->pw_expiration < req_time) || + (p->attributes & KRB5_KDB_REQUIRES_PWCHANGE)) { + kadm5_free_principal_ent(handle, p); + return KABADUSER; + } + + /* + * See if the principal is locked out + */ + + if (p->attributes & KRB5_KDB_DISALLOW_ALL_TIX) { + kadm5_free_principal_ent(handle, p); + return KABADUSER; + } + + /* + * There's no way we can handle hardware preauth, so + * disallow tickets with this flag set. + */ + + if (p->attributes & KRB5_KDB_REQUIRES_HW_AUTH) { + kadm5_free_principal_ent(handle, p); + return KABADUSER; + } + + /* + * Must be okay, then + */ + + return 0; +} + + +/* + * Create an rx reply packet in "packet" using the provided data. + * The caller is responsible for zeroing key and sched. + */ + +int make_reply_packet(context, handle, reply, challenge_response, start_time, + end_time, cname, cinst, sname, sinst, key, sched, label) +krb5_context context; +void *handle; +packet_t reply; +int challenge_response, start_time, end_time; +char *cname, *cinst, *sname, *sinst; +des_cblock key; +des_key_schedule sched; +char *label; +{ + int rv, n, maxn, v4life, *enclenp, *ticklenp; + u_char *p, *enc, *ticket; + kadm5_principal_ent_rec cprinc, sprinc; + des_cblock skey, new_session_key; + des_key_schedule ssched; + krb5_deltat lifetime; + + rv = 0; + + rv = check_princ(context, handle, cname, cinst, &cprinc); + if (rv) + return rv; + + rv = check_princ(context, handle, sname, sinst, &sprinc); + if (rv) { + kadm5_free_principal_ent(handle, &cprinc); + return rv; + } + + /* + * Bound ticket lifetime by max lifetimes of user and service. + * + * Since V5 already stores everything in Unix epoch timestamps like + * AFS, these calculations are much simpler. + */ + + lifetime = end_time - start_time; + lifetime = min(lifetime, cprinc.max_life); + lifetime = min(lifetime, sprinc.max_life); + lifetime = min(lifetime, realm_params.max_life); + + end_time = start_time + lifetime; + + /* + * But we have to convert back to V4-style lifetimes + */ + + v4life = lifetime / 300; + if (v4life > 127) { + /* + * Use the CMU algorithm instead + */ + long *clist = cmu_seconds; + while (*clist && *clist < lifetime) clist++; + v4life = 128 + (clist - cmu_seconds); + } + + /* + * If this is for afs and the instance is the local cell name + * then we assume we added the instance in GetTickets to + * identify the afs key in the kerberos database. This is for + * cases where the afs cell name is different from the kerberos + * realm name. We now want to remove the instance so it doesn't + * cause klog to barf. + */ + if (!strcmp(sname, "afs") && (strcasecmp(sinst, localcell) == 0)) + sinst[0] = '\0'; + + /* + * All the data needed to construct the ticket is ready, so do it. + */ + + p = (unsigned char *) reply->base; + maxn = reply->len; + n = 0; + +#define ERR(x) do { rv = x ; goto error; } while (0) +#define ADVANCE(x) { if ((n += x) > maxn) ERR(KAINTERNALERROR); else p += x;} +#define PUT_CHAR(x) { *p = (x); ADVANCE(1); } +#define PUT_INT(x) { int q = ntohl(x); memcpy(p, (char *)&q, 4); ADVANCE(4); } +#define PUT_STR(x) { strcpy((char *) p, x); ADVANCE(strlen(x) + 1); } + + ADVANCE(28); + PUT_INT(0x2bc); + + enclenp = (int *)p; + PUT_INT(0); /* filled in later */ + + enc = p; + PUT_INT(0); + PUT_INT(challenge_response); + + /* + * new_session_key is created here, and remains in the clear + * until just before we return. + */ + des_new_random_key(new_session_key); + memcpy(p, new_session_key, 8); + + ADVANCE(8); + PUT_INT(start_time); + PUT_INT(end_time); + PUT_INT(sprinc.kvno); + + ticklenp = (int *)p; + PUT_INT(0); /* filled in later */ + + PUT_STR(cname); + PUT_STR(cinst); + PUT_STR(""); + PUT_STR(sname); + PUT_STR(sinst); + + ticket = p; + PUT_CHAR(0); /* flags, always 0 */ + PUT_STR(cname); + PUT_STR(cinst); + PUT_STR(""); + PUT_INT(0); /* would be ip address */ + + memcpy(p, new_session_key, 8); + + ADVANCE(8); + + PUT_CHAR(v4life); + PUT_INT(start_time); + PUT_STR(sname); + PUT_STR(sinst); + + ADVANCE(PAD_TO(p - ticket, 8) - (p - ticket)); + + *ticklenp = ntohl(p - ticket); + + rv = get_princ_key(context, handle, &sprinc, skey, ssched); + if (rv) + return rv; + des_pcbc_encrypt((C_Block *) ticket, (C_Block *) ticket, p - ticket, + ssched, (C_Block *) skey, ENCRYPT); + memset(skey, 0, sizeof(skey)); + memset(ssched, 0, sizeof(ssched)); + + PUT_STR(label); /* "tgsT" or "gtkt" */ + ADVANCE(-1); /* back up over string terminator */ + + ADVANCE(PAD_TO(p - enc, 8) - (p - enc)); +#undef ERR +#undef ADVANCE +#undef PUT_CHAR +#undef PUT_INT +#undef PUT_STR + + *enclenp = ntohl(p - enc); + des_pcbc_encrypt((C_Block *) enc, (C_Block *) enc, p - enc, sched, + (C_Block *) key, ENCRYPT); + reply->len = n; + + error: + memset(new_session_key, 0, sizeof(new_session_key)); + kadm5_free_principal_ent(handle, &cprinc); + kadm5_free_principal_ent(handle, &sprinc); + + return rv; +} + +#define ERR(x) do { rv = x; goto error; } while (0) +#define ADVANCE(x) { if ((n += x) > maxn) ERR(KABADREQUEST); else p += x; } +#define GET_INT(x) { int q; memcpy((char *)&q, p, 4); x = ntohl(q); ADVANCE(4); } +#define GET_CHAR(x) { x = *p; ADVANCE(1); } +#define GET_PSTR(x) \ + { \ + GET_INT(len); \ + if (len > sizeof(x) - 1) ERR(KABADREQUEST); \ + memcpy(x, p, len); \ + x[len] = 0; \ + ADVANCE(PAD_TO(len, 4)); \ + } + +#define GET_STR(x) \ + { \ + len = strlen(p); \ + if (len > sizeof(x) - 1) ERR(KABADREQUEST); \ + strcpy(x, p); \ + ADVANCE(len + 1); \ + } + + +/* + * Process an Authenticate request. + */ + +int Authenticate(context, handle, from, req, reply) +krb5_context context; +void *handle; +char *from; +packet_t req, reply; +{ + int rv, n, maxn; + int len, start_time, end_time, challenge; + char name[ANAME_SZ+1], inst[INST_SZ+1], *p; + kadm5_principal_ent_rec cprinc; + des_cblock ckey; + des_key_schedule csched; + int free_princ_ent = 0; + + rv = 0; + + p = req->base; + maxn = req->len; + n = 0; + + ADVANCE(32); + + GET_PSTR(name); + GET_PSTR(inst); + + if (debug) + fprintf(stderr, "Authenticating %s.%s\n", name, inst); + + rv = check_princ(context, handle, name, inst, &cprinc); + if (rv) + ERR(rv); + + free_princ_ent = 1; + + GET_INT(start_time); + GET_INT(end_time); + + GET_INT(len); + if (len != 8) + ERR(KABADREQUEST); + + /* + * ckey and csched are set here and remain in the clear + * until just before we return. + */ + + rv = get_princ_key(context, handle, &cprinc, ckey, csched); + if (rv) + ERR(rv); + des_pcbc_encrypt((C_Block *) p, (C_Block *) p, 8, csched, + (C_Block *) ckey, DECRYPT); + + GET_INT(challenge); + + rv = memcmp(p, "gTGS", 4); + if (rv) + ERR(KABADREQUEST); + ADVANCE(4); + + /* ignore the rest */ + ADVANCE(8); + + /* + * We have all the data from the request, now generate the reply. + */ + + rv = make_reply_packet(context, handle, reply, challenge + 1, start_time, + end_time, name, inst, "krbtgt", localcell, + ckey, csched, "tgsT"); + error: + memset(ckey, 0, sizeof(ckey)); + memset(csched, 0, sizeof(csched)); + + syslog(LOG_INFO, "authenticate: %s.%s from %s", name, inst, from); + if (rv) { + syslog(LOG_INFO, "... failed due to %s", kaerror(rv)); + } + if (free_princ_ent) + kadm5_free_principal_ent(handle, &cprinc); + return rv; +} + + +/* + * Process a GetTicket rpc. + */ + +int GetTicket(context, handle, from, req, reply) +krb5_context context; +void *handle; +char *from; +packet_t req, reply; +{ + int rv, n, maxn, len, ticketlen; + char *p; + u_int kvno, start_time, end_time, times[2], flags, ipaddr; + u_int tgt_start_time, tgt_end_time, lifetime; + char rname[ANAME_SZ+1], rinst[INST_SZ+1]; /* requested principal */ + char sname[ANAME_SZ+1], sinst[INST_SZ+1]; /* service principal (TGT) */ + char cname[ANAME_SZ+1], cinst[INST_SZ+1]; /* client principal */ + char cell[REALM_SZ+1], realm[REALM_SZ+1]; + char enctimes[8 + 1], ticket[1024]; + u_char tgt_lifetime; + kadm5_principal_ent_rec cprinc; + des_cblock ckey, session_key; + des_key_schedule csched, session_sched; + int free_princ_ent = 0; + + rv = 0; + + /* + * Initialize these so we don't crash trying to print them in + * case they don't get filled in. + */ + strcpy(rname, "Unknown"); + strcpy(rinst, "Unknown"); + strcpy(sname, "Unknown"); + strcpy(sinst, "Unknown"); + strcpy(cname, "Unknown"); + strcpy(cinst, "Unknown"); + strcpy(cell, "Unknown"); + strcpy(realm, "Unknown"); + + p = req->base; + maxn = req->len; + n = 0; + + ADVANCE(32); + + GET_INT(kvno); + + GET_PSTR(cell); + if (!cell[0]) + strcpy(cell, localcell); + + if (debug) + fprintf(stderr, "Cell is %s\n", cell); + + memset(ticket, 0, sizeof(ticket)); + GET_PSTR(ticket); + ticketlen = len; /* hacky hack hack */ + GET_PSTR(rname); + GET_PSTR(rinst); + + if (debug) + fprintf(stderr, "Request for %s/%s\n", rname, rinst); + + GET_PSTR(enctimes); /* still encrypted */ + if (len != 8) /* hack and hack again */ + ERR(KABADREQUEST); + + /* ignore the rest */ + ADVANCE(8); + + /* + * That's it for the packet, now decode the embedded ticket. + */ + + rv = check_princ(context, handle, "krbtgt", cell, &cprinc); + if (rv) + ERR(rv); + + free_princ_ent = 1; + + rv = get_princ_key(context, handle, &cprinc, ckey, csched); + if (rv) + ERR(rv); + des_pcbc_encrypt((C_Block *) ticket, (C_Block *) ticket, ticketlen, csched, + (C_Block *) ckey, DECRYPT); + memset(ckey, 0, sizeof(ckey)); + memset(csched, 0, sizeof(csched)); + + /* + * The ticket's session key is now in the clear in the ticket buffer. + * We zero it just before returning. + */ + + p = ticket; + maxn = ticketlen; + n = 0; + + GET_CHAR(flags); + GET_STR(cname); + GET_STR(cinst); + GET_STR(realm); + GET_INT(ipaddr); + memcpy(session_key, p, 8); + ADVANCE(8); + + GET_CHAR(tgt_lifetime); + GET_INT(tgt_start_time); + GET_STR(sname); + GET_STR(sinst); + + if (debug) + fprintf(stderr, + "ticket: %s.%s@%s for %s.%s\n", + cname, cinst, realm, sname, sinst); + + /* + * ok, we've got the ticket unpacked. + * now decrypt the start and end times. + */ + + rv = des_key_sched(session_key, session_sched); + if (rv) + ERR(KABADTICKET); + + des_ecb_encrypt((C_Block *) enctimes, (C_Block *) times, session_sched, + DECRYPT); + start_time = ntohl(times[0]); + end_time = ntohl(times[1]); + + /* + * All the info we need is now available. + * Now validate the request. + */ + + /* + * This translator requires that the flags and IP address + * in the ticket be zero, because we always set them that way, + * and we want to accept only tickets that we generated. + * + * Are the flags and IP address fields 0? + */ + if (flags || ipaddr) { + if (debug) + fprintf(stderr, "ERROR: flags or ipaddr field non-zero\n"); + ERR(KABADTICKET); + } + /* + * Is the supplied ticket a tgt? + */ + if (strcmp(sname, "krbtgt")) { + if (debug) + fprintf(stderr, "ERROR: not for krbtgt service\n"); + ERR(KABADTICKET); + } + + /* + * This translator does not allow MIT-style cross-realm access. + * Is this a cross-realm ticket? + */ + if (strcasecmp(sinst, localcell)) { + if (debug) + fprintf(stderr, + "ERROR: Service instance (%s) differs from local cell\n", + sinst); + ERR(KABADTICKET); + } + + /* + * This translator does not issue cross-realm tickets, + * since klog doesn't use this feature. + * Is the request for a cross-realm ticket? + */ + if (strcasecmp(cell, localcell)) { + if (debug) + fprintf(stderr, "ERROR: Cell %s != local cell", cell); + ERR(KABADTICKET); + } + + /* + * Even if we later decide to issue cross-realm tickets, + * we should not permit "realm hopping". + * This means that the client's realm should match + * the realm of the tgt with whose key we are supposed + * to decrypt the ticket. I think. + */ + if (*realm && strcasecmp(realm, cell)) { + if (debug) + fprintf(stderr, "ERROR: Realm %s != cell %s\n", realm, cell); + ERR(KABADTICKET); + } + + /* + * This translator issues service tickets only for afs, + * since klog is the only client that should be using it. + * Is the requested service afs? + * + * Note: to make EMT work, we're allowing tickets for emt/admin and + * adm/admin. + */ + if (! ((strcmp(rname, "afs") == 0 && ! *rinst) || + (strcmp(rname, "emt") == 0 && strcmp(rinst, "admin") == 0) || + (strcmp(rname, "adm") == 0 && strcmp(rinst, "admin") == 0))) + ERR(KABADSERVER); + + /* + * If the local realm name and cell name differ and the user + * is in the local cell and has requested a ticket of afs. (no + * instance, then we actually want to get a ticket for + * afs/@ + */ + if ((strcmp(rname, "afs") == 0) && !*rinst && + strcmp(localrealm, localcell) && + (strcasecmp(cell, localcell) == 0)) { + char *c; + + strcpy(rinst, localcell); + + for (c = rinst; *c != NULL; c++) + *c = (char) tolower( (int) *c); + + if (debug) + fprintf(stderr, "Getting ticket for afs/%s\n", localcell); + } + + /* + * Even if we later decide to issue service tickets for + * services other than afs, we should still disallow + * the "changepw" and "krbtgt" services. + */ + if (!strcmp(rname, "changepw") || !strcmp(rname, "krbtgt")) + ERR(KABADSERVER); + + /* + * Is the tgt valid yet? (ie. is the start time in the future) + */ + if (req_time < tgt_start_time - CLOCK_SKEW) { + if (debug) + fprintf(stderr, "ERROR: Ticket not yet valid\n"); + ERR(KABADTICKET); + } + + /* + * Has the tgt expired? (ie. is the end time in the past) + * + * Sigh, convert from V4 lifetimes back to Unix epoch times. + */ + + if (tgt_lifetime < 128) + tgt_end_time = tgt_start_time + tgt_lifetime * 300; + else if (tgt_lifetime < 192) + tgt_end_time = tgt_start_time + cmu_seconds[tgt_lifetime - 128]; + else + tgt_end_time = tgt_start_time + MAX_TICKET_LIFETIME; + + if (tgt_end_time < req_time) { + if (debug) + fprintf(stderr, "ERROR: Ticket expired\n"); + ERR(KABADTICKET); + } + + /* + * This translator uses the requested start time as a cheesy + * authenticator, since the KA protocol does not have an + * explicit authenticator. We can do this since klog always + * requests a start time equal to the current time. + * + * Is the requested start time approximately now? + */ + if (abs(req_time - start_time) > CLOCK_SKEW) + ERR(KACLOCKSKEW); + + /* + * The new ticket's lifetime is the minimum of: + * 1. remainder of tgt's lifetime + * 2. requested lifetime + * + * This is further limited by the client and service's max lifetime + * in make_reply_packet(). + */ + + lifetime = tgt_end_time - req_time; + lifetime = min(lifetime, end_time - start_time); + end_time = req_time + lifetime; + + /* + * We have all the data from the request, now generate the reply. + */ + + rv = make_reply_packet(context, handle, reply, 0, start_time, end_time, + cname, cinst, rname, rinst, + session_key, session_sched, "gtkt"); + error: + memset(ticket, 0, sizeof(ticket)); + memset(session_key, 0, sizeof(session_key)); + memset(session_sched, 0, sizeof(session_sched)); + + if (free_princ_ent) + kadm5_free_principal_ent(handle, &cprinc); + + syslog(LOG_INFO, "getticket: %s.%s from %s for %s.%s", + cname, cinst, from, rname, rinst); + if (rv) { + syslog(LOG_INFO, "... failed due to %s", kaerror(rv)); + } + return rv; +} + + +#undef ERR +#undef ADVANCE +#undef GET_INT +#undef GET_PSTR +#undef GET_STR + +/* + * Convert the request into a reply. + * Returns 0 on success. + */ + +void process(context, handle, from, req, reply) +krb5_context context; +void *handle; +char *from; +packet_t req, reply; +{ + int rv; + rx_t req_rx = (rx_t)req->base; + rx_t reply_rx = (rx_t)reply->base; + int service, request; + + service = ntohs(req_rx->rx_service); + request = ntohl(req_rx->rx_request); + + /* ignore everything but type 1 */ + if (req_rx->rx_type != 1) { + reply->len = 0; + return; + } + + /* copy the rx header and change the flags */ + *reply_rx = *req_rx; + reply_rx->rx_flags = 4; + + rv = -1; + + if (service == 0x2db && (request == 0x15 || request == 0x16)) { + if (debug) + fprintf(stderr, "Handling Authenticate request\n"); + rv = Authenticate(context, handle, from, req, reply); + } + if (service == 0x2dc && request == 0x17) { + if (debug) + fprintf(stderr, "Handling GetTicket request\n"); + rv = GetTicket(context, handle, from, req, reply); + } +/* + if (service == 0x2db && request == 0x1) { + rv = Authenticate_old(from, req, reply); + } + if (service == 0x2dc && request == 0x3) { + rv = GetTicket_old(from, req, reply); + } + */ + if (rv == -1) { + syslog(LOG_INFO, "bogus request %d/%d", service, request); + rv = KABADREQUEST; + } + + if (rv) { + /* send the error back to rx */ + reply->len = sizeof (*reply_rx); + + reply_rx->rx_type = 4; + reply_rx->rx_flags = 0; + reply_rx->rx_request = ntohl(rv); + } +} + + +int main(argc, argv) +int argc; +char **argv; +{ + int s, rv, ch, mflag = 0; + u_short port; + struct sockaddr_in sin; + int forwarders[MAXFORWARDERS], num_forwarders; + krb5_context context; + krb5_error_code code; + krb5_keyblock mkey; + krb5_principal master_princ; + kadm5_principal_ent_rec master_princ_rec; + void *handle; + facility_mapping *mapping; + int facility = LOG_DAEMON; + + extern char *optarg; + + port = 7004; + num_forwarders = 0; + + /* + * Parse args. + */ + while ((ch = getopt(argc, argv, "c:df:l:mp:r:")) != -1) { + switch (ch) { + case 'c': + localcell = optarg; + break; + case 'd': + debug++; + break; + case 'f': { + struct hostent *hp; + + if (num_forwarders++ >= MAXFORWARDERS) + pexit("too many forwarders\n"); + + hp = gethostbyname(optarg); + if (!hp) { + printf("unknown host %s\n", optarg); + exit(1); + } + forwarders[num_forwarders - 1] = *(int *)hp->h_addr; + + break; + } + case 'l': + for (mapping = mappings; mapping->string != NULL; mapping++) + if (strcmp(mapping->string, optarg) == 0) + break; + + if (mapping->string == NULL) { + printf("Unknown facility \"%s\"\n", optarg); + exit(1); + } + + facility = mapping->num; + break; + case 'm': + mflag = 1; + break; + case 'p': + if (isdigit(*optarg)) { + port = atoi(optarg); + } + else { + struct servent *sp; + + sp = getservbyname(optarg, "udp"); + if (!sp) { + printf("unknown service %s\n", optarg); + exit(1); + } + port = sp->s_port; + } + break; + case 'r': + localrealm = optarg; + break; + default: + printf("usage: %s [-c cell] [-d] [-f forwarder-host] [-l facility ] [-p port] [-r realm]\n", + argv[0]); + exit(1); + } + } + + openlog("fakeka", LOG_PID, facility); + + port = htons(port); + + /* + * Set up the socket. + */ + + s = socket(AF_INET, SOCK_DGRAM, 0); + if (s < 0) + perrorexit("Couldn't create socket"); + + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = 0; + sin.sin_port = port; + + rv = bind(s, (struct sockaddr *)&sin, sizeof(sin)); + if (rv < 0) + perrorexit("Couldn't bind socket"); + + /* + * Initialize kerberos stuff and kadm5 stuff. + */ + + if ((code = krb5_init_context(&context))) { + com_err(argv[0], code, "while initializing Kerberos"); + exit(1); + } + + if (!localrealm && (code = krb5_get_default_realm(context, &localrealm))) { + com_err(argv[0], code, "while getting local realm"); + exit(1); + } + + if (!localcell) + localcell = localrealm; + + if ((code = kadm5_init_with_password(progname, NULL, KADM5_ADMIN_SERVICE, + NULL, KADM5_STRUCT_VERSION, + KADM5_API_VERSION_2, &handle))) { + com_err(argv[0], code, "while initializing Kadm5"); + exit(1); + } + + if ((code = kadm5_get_config_params(context, NULL, NULL, NULL, + &realm_params))) { + com_err(argv[0], code, "while getting realm parameters"); + exit(1); + } + + if (! (realm_params.mask & KADM5_CONFIG_MAX_LIFE)) { + fprintf(stderr, "Cannot determine maximum ticket lifetime\n"); + exit(1); + } + + /* + * We need to initialize the random number generator for DES. Use + * the master key to do this. + */ + + if ((code = krb5_parse_name(context, realm_params.mask & + KADM5_CONFIG_MKEY_NAME ? + realm_params.mkey_name : "K/M", + &master_princ))) { + com_err(argv[0], code, "while parsing master key name"); + exit(1); + } + + if ((code = kadm5_get_principal(handle, master_princ, &master_princ_rec, + KADM5_KEY_DATA))) { + com_err(argv[0], code, "while getting master key data"); + exit(1); + } + + if ((code = kadm5_decrypt_key(handle, &master_princ_rec, + ENCTYPE_DES_CBC_CRC, -1, 0, &mkey, NULL, + NULL))) { + com_err(argv[0], code, "while decrypting the master key"); + exit(1); + } + + des_init_random_number_generator(mkey.contents); + + krb5_free_keyblock_contents(context, &mkey); + + kadm5_free_principal_ent(handle, &master_princ_rec); + + krb5_free_principal(context, master_princ); + + /* + * Fork and go into the background, if requested + */ + + if (!debug && mflag && daemon(0, 0)) { + com_err(argv[0], errno, "while detaching from tty"); + } + + /* + * rpc server loop. + */ + + for (;;) { + struct packet req, reply; + int sinlen, packetlen, i, forwarded; + char *from; + + sinlen = sizeof(sin); + forwarded = 0; + + memset(req.data, 0, sizeof(req.data)); + rv = recvfrom(s, req.data, sizeof(req.data), + 0, (struct sockaddr *)&sin, &sinlen); + + if (rv < 0) { + syslog(LOG_ERR, "recvfrom failed: %m"); + sleep(1); + continue; + } + packetlen = rv; + + for (i = 0; i < num_forwarders; i++) { + if (sin.sin_addr.s_addr == forwarders[i]) { + forwarded = 1; + break; + } + } + + if ((code = krb5_timeofday(context, &req_time))) { + syslog(LOG_ERR, "krb5_timeofday failed: %s", + error_message(code)); + continue; + } + + memset(reply.data, 0, sizeof(reply.data)); + req.len = packetlen; + req.base = req.data; + reply.base = reply.data; + reply.len = sizeof(reply.data); + + if (forwarded) { + struct in_addr ia; + + memcpy(&ia.s_addr, req.data, 4); + from = inet_ntoa(ia); + /* + * copy the forwarder header and adjust the bases and lengths. + */ + memcpy(reply.data, reply.data, HEADER_LEN); + req.base += HEADER_LEN; + req.len -= HEADER_LEN; + reply.base += HEADER_LEN; + reply.len -= HEADER_LEN; + } + else { + from = inet_ntoa(sin.sin_addr); + } + + process(context, handle, from, &req, &reply); + + if (reply.len == 0) + continue; + + if (forwarded) { + /* re-adjust the length to account for the forwarder header */ + reply.len += HEADER_LEN; + } + + rv = sendto(s, reply.data, reply.len, + 0, (struct sockaddr *)&sin, sinlen); + if (rv < 0) { + syslog(LOG_ERR, "sendto failed: %m"); + sleep(1); + } + } + /*NOTREACHED*/ +} -- 2.26.2