--- /dev/null
+/*
+ * 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 <kenh@cmf.nrl.navy.mil>
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <ctype.h>
+#include <netdb.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_MEMORY_H
+#include <memory.h>
+#endif
+
+#include <krb5.h>
+#include <kadm5/admin.h>
+#include <com_err.h>
+#include <kerberosIV/krb.h>
+#include <kerberosIV/des.h>
+
+#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/<cell name>@<realm name>
+ */
+ 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*/
+}