/*
 * Copyright 1985, 1986, 1987, 1988 by the Massachusetts Institute
 * of Technology.
 *
 * For copying and distribution information, please see the file
 * <mit-copyright.h>.
 */

#include <windows.h>
#include <des.h>
#include <krb.h>
#include <prot.h>
#include <timeval.h>
#include <string.h>

extern int krb_ap_req_debug;

static struct timeval t_local = { 0, 0 };

/*
 * Keep the following information around for subsequent calls
 * to this routine by the same server using the same key.
 */

static Key_schedule serv_key;   /* Key sched to decrypt ticket */
static C_Block ky;              /* Initialization vector */
static int st_kvno;             /* version number for this key */
static char st_rlm[REALM_SZ];   /* server's realm */
static char st_nam[ANAME_SZ];   /* service name */
static char st_inst[INST_SZ];   /* server's instance */

/*
 * This file contains two functions.  krb_set_key() takes a DES
 * key or password string and returns a DES key (either the original
 * key, or the password converted into a DES key) and a key schedule
 * for it.
 *
 * krb_rd_req() reads an authentication request and returns information
 * about the identity of the requestor, or an indication that the
 * identity information was not authentic.
 */

/*
 * krb_set_key() takes as its first argument either a DES key or a
 * password string.  The "cvt" argument indicates how the first
 * argument "key" is to be interpreted: if "cvt" is null, "key" is
 * taken to be a DES key; if "cvt" is non-null, "key" is taken to
 * be a password string, and is converted into a DES key using
 * string_to_key().  In either case, the resulting key is returned
 * in the external static variable "ky".  A key schedule is
 * generated for "ky" and returned in the external static variable
 * "serv_key".
 *
 * This routine returns the return value of des_key_sched.
 *
 * krb_set_key() needs to be in the same .o file as krb_rd_req() so that
 * the key set by krb_set_key() is available in private storage for
 * krb_rd_req().
 */

int
krb_set_key(key,cvt)
    char *key;
    int cvt;
{
#ifdef NOENCRYPTION
    bzero(ky, sizeof(ky));
    return KSUCCESS;
#else /* Encrypt */
    if (cvt)
        string_to_key(key,ky);
    else
        bcopy(key,(char *)ky,8);
    return(des_key_sched(ky,serv_key));
#endif /* NOENCRYPTION */
}


/*
 * krb_rd_req() takes an AUTH_MSG_APPL_REQUEST or
 * AUTH_MSG_APPL_REQUEST_MUTUAL message created by krb_mk_req(),
 * checks its integrity and returns a judgement as to the requestor's
 * identity.
 *
 * The "authent" argument is a pointer to the received message.
 * The "service" and "instance" arguments name the receiving server,
 * and are used to get the service's ticket to decrypt the ticket
 * in the message, and to compare against the server name inside the
 * ticket.  "from_addr" is the network address of the host from which
 * the message was received; this is checked against the network
 * address in the ticket.  If "from_addr" is zero, the check is not
 * performed.  "ad" is an AUTH_DAT structure which is
 * filled in with information about the sender's identity according
 * to the authenticator and ticket sent in the message.  Finally,
 * "fn" contains the name of the file containing the server's key.
 * (If "fn" is NULL, the server's key is assumed to have been set
 * by krb_set_key().  If "fn" is the null string ("") the default
 * file KEYFILE, defined in "krb.h", is used.)
 *
 * krb_rd_req() returns RD_AP_OK if the authentication information
 * was genuine, or one of the following error codes (defined in
 * "krb.h"):
 *
 *      RD_AP_VERSION           - wrong protocol version number
 *      RD_AP_MSG_TYPE          - wrong message type
 *      RD_AP_UNDEC             - couldn't decipher the message
 *      RD_AP_INCON             - inconsistencies found
 *      RD_AP_BADD              - wrong network address
 *      RD_AP_TIME              - client time (in authenticator)
 *                                too far off server time
 *      RD_AP_NYV               - Kerberos time (in ticket) too
 *                                far off server time
 *      RD_AP_EXP               - ticket expired
 *
 * For the message format, see krb_mk_req().
 *
 * Mutual authentication is not implemented.
 */

krb_rd_req(authent,service,instance,from_addr,ad,fn)
    register KTEXT authent;     /* The received message */
    char *service;              /* Service name */
    char *instance;             /* Service instance */
    long from_addr;             /* Net address of originating host */
    AUTH_DAT *ad;               /* Structure to be filled in */
    char *fn;                   /* Filename to get keys from */
{
    static KTEXT_ST ticket;     /* Temp storage for ticket */
    static KTEXT tkt = &ticket;
    static KTEXT_ST req_id_st;  /* Temp storage for authenticator */
    register KTEXT req_id = &req_id_st;

    char realm[REALM_SZ];       /* Realm of issuing kerberos */
    static Key_schedule seskey_sched; /* Key sched for session key */
    unsigned char skey[KKEY_SZ]; /* Session key from ticket */
    char sname[SNAME_SZ];       /* Service name from ticket */
    char iname[INST_SZ];        /* Instance name from ticket */
    char r_aname[ANAME_SZ];     /* Client name from authenticator */
    char r_inst[INST_SZ];       /* Client instance from authenticator */
    char r_realm[REALM_SZ];     /* Client realm from authenticator */
    unsigned int r_time_ms;     /* Fine time from authenticator */
    unsigned long r_time_sec;   /* Coarse time from authenticator */
    register char *ptr;         /* For stepping through */
    unsigned long delta_t;      /* Time in authenticator - local time */
    long tkt_age;               /* Age of ticket */
    static int swap_bytes;      /* Need to swap bytes? */
    static int mutual;          /* Mutual authentication requested? */
    static unsigned char s_kvno;/* Version number of the server's key
                                 * Kerberos used to encrypt ticket */
    int status;

    tkt->mbz = req_id->mbz = 0;

    if (authent->length <= 0)
        return(RD_AP_MODIFIED);

    ptr = (char *) authent->dat;

    /* get msg version, type and byte order, and server key version */

    /* check version */
    if (KRB_PROT_VERSION != (unsigned int) *ptr++)
        return(RD_AP_VERSION);

    /* byte order */
    swap_bytes = 0;
    if ((*ptr & 1) != HOST_BYTE_ORDER)
        swap_bytes++;

    /* check msg type */
    mutual = 0;
    switch (*ptr++ & ~1) {
    case AUTH_MSG_APPL_REQUEST:
        break;
    case AUTH_MSG_APPL_REQUEST_MUTUAL:
        mutual++;
        break;
    default:
        return(RD_AP_MSG_TYPE);
    }

#ifdef lint
    /* XXX mutual is set but not used; why??? */
    /* this is a crock to get lint to shut up */
    if (mutual)
        mutual = 0;
#endif /* lint */
    s_kvno = *ptr++;            /* get server key version */
    (void) strncpy(realm,ptr,REALM_SZ); /* And the realm of the issuing KDC */
    realm[REALM_SZ-1] = '\0';
    ptr += strlen(realm) + 1;     /* skip the realm "hint" */

    /*
     * If "fn" is NULL, key info should already be set; don't
     * bother with ticket file.  Otherwise, check to see if we
     * already have key info for the given server and key version
     * (saved in the static st_* variables).  If not, go get it
     * from the ticket file.  If "fn" is the null string, use the
     * default ticket file.
     */
    if (fn && (strcmp(st_nam,service) || strcmp(st_inst,instance) ||
               strcmp(st_rlm,realm) || (st_kvno != s_kvno))) {
        if (*fn == 0) fn = KEYFILE;
        st_kvno = s_kvno;
#ifndef NOENCRYPTION
        if (read_service_key(service,instance,realm,(int) s_kvno,
                            fn,(char *)skey))
            return(RD_AP_UNDEC);
        if (status = krb_set_key((char *)skey,0))
            return(status);
#endif /* !NOENCRYPTION */
        (void) strcpy(st_rlm,realm);
        (void) strcpy(st_nam,service);
        (void) strcpy(st_inst,instance);
    }

    /* Get ticket from authenticator */
    tkt->length = (int) *ptr++;
    if ((tkt->length + (ptr+1 - (char *) authent->dat)) > authent->length)
        return(RD_AP_MODIFIED);
    bcopy(ptr+1,(char *)(tkt->dat),tkt->length);

    if (krb_ap_req_debug)
        klog("ticket->length: %d",tkt->length);

#ifndef NOENCRYPTION
    /* Decrypt and take apart ticket */
#endif

    if (k_decomp_ticket(tkt,&ad->k_flags,ad->pname,ad->pinst,ad->prealm,
                      &(ad->address),ad->session, &(ad->life),
                      &(ad->time_sec),sname,iname,ky,serv_key))
        return(RD_AP_UNDEC);

    if (krb_ap_req_debug) {
        klog("Ticket Contents.");
        klog(" Aname:   %s.%s",ad->pname,
            ((int)*(ad->prealm) ? ad->prealm : "Athena"));
        klog(" Service: %s%s%s",sname,((int)*iname ? "." : ""),iname);
    }

    /* Extract the authenticator */
    req_id->length = (int) *(ptr++);
    if ((req_id->length + (ptr + tkt->length - (char *) authent->dat)) >
        authent->length)
        return(RD_AP_MODIFIED);
    bcopy(ptr + tkt->length, (char *)(req_id->dat),req_id->length);

#ifndef NOENCRYPTION
    /* And decrypt it with the session key from the ticket */
    if (krb_ap_req_debug) klog("About to decrypt authenticator");
    key_sched(ad->session,seskey_sched);
    pcbc_encrypt((C_Block *)req_id->dat,(C_Block *)req_id->dat,
                 (long) req_id->length, seskey_sched,ad->session,DES_DECRYPT);
    if (krb_ap_req_debug) klog("Done.");
#endif /* NOENCRYPTION */

#define check_ptr() if ((ptr - (char *) req_id->dat) > req_id->length) return(RD_AP_MODIFIED);

    ptr = (char *) req_id->dat;
    (void) strncpy(r_aname,ptr,ANAME_SZ); /* Authentication name */
    r_aname[ANAME_SZ-1] = '\0';
    ptr += strlen(r_aname)+1;
    check_ptr();
    (void) strncpy(r_inst,ptr,INST_SZ);  /* Authentication instance */
    r_inst[INST_SZ-1] = '\0';
    ptr += strlen(r_inst)+1;
    check_ptr();
    (void) strncpy(r_realm,ptr,REALM_SZ); /* Authentication name */
    r_realm[REALM_SZ-1] = '\0';
    ptr += strlen(r_realm)+1;
    check_ptr();
    bcopy(ptr,(char *)&ad->checksum,4); /* Checksum */
    ptr += 4;
    check_ptr();
    if (swap_bytes) swap_u_long(ad->checksum);
    r_time_ms = *(ptr++);       /* Time (fine) */
#ifdef lint
    /* XXX r_time_ms is set but not used.  why??? */
    /* this is a crock to get lint to shut up */
    if (r_time_ms)
        r_time_ms = 0;
#endif /* lint */
    check_ptr();
    /* assume sizeof(r_time_sec) == 4 ?? */
    bcopy(ptr,(char *)&r_time_sec,4); /* Time (coarse) */
    if (swap_bytes) swap_u_long(r_time_sec);

    /* Check for authenticity of the request */
    if (krb_ap_req_debug)
        klog("Pname:   %s %s",ad->pname,r_aname);
    if (strcmp(ad->pname,r_aname) != 0)
        return(RD_AP_INCON);
    if (strcmp(ad->pinst,r_inst) != 0)
        return(RD_AP_INCON);
    if (krb_ap_req_debug)
        klog("Realm:   %s %s",ad->prealm,r_realm);
    if ((strcmp(ad->prealm,r_realm) != 0))
        return(RD_AP_INCON);

    if (krb_ap_req_debug)
        klog("Address: %d %d",ad->address,from_addr);
    if (from_addr && (ad->address != from_addr))
        return(RD_AP_BADD);

    (void) gettimeofday(&t_local,(struct timezone *) 0);
    delta_t = abs((int)(t_local.tv_sec - r_time_sec));
    if (delta_t > CLOCK_SKEW) {
        if (krb_ap_req_debug)
            klog("Time out of range: %d - %d = %d",
                 t_local.tv_sec,r_time_sec,delta_t);
        return(RD_AP_TIME);
    }

    /* Now check for expiration of ticket */

    tkt_age = t_local.tv_sec - ad->time_sec;
    if (krb_ap_req_debug)
        klog("Time: %d Issue Date: %d Diff: %d Life %x",
             t_local.tv_sec,ad->time_sec,tkt_age,ad->life);

    if (t_local.tv_sec < ad->time_sec) {
        if ((ad->time_sec - t_local.tv_sec) > CLOCK_SKEW)
            return(RD_AP_NYV);
    }
#if 0
    else if ((t_local.tv_sec - ad->time_sec) > 5 * 60 * ad->life)
        return(RD_AP_EXP);
#else
    else if (krb_life_to_time(ad->time_sec, ad->life)
	     < t_local.tv_sec + CLOCK_SKEW) {
        /*
	 * This calculation is different than the same expiration
	 * calculation in  krb5.  In krb5  the ticket lasts for
	 * clock_skew seconds longer than its expiration; in krb4 it
	 * lasts clock_skew seconds less.  This difference is
	 * necessary to avoid using an almost expired tgt to get a new
	 * tgt that will last for another 5 minutes.  This code
	 * interacts with the login in src/kdc/kerberos_v4.c to
	 * back-date tickets to avoid them expiring late.  The
	 * combination may be overly conservative, but I'm fairly sure
	 * either  removing the kerberos_v4 backdating or replacing
	 * this check with the krb5 check is sufficient to create a
	 * security problem.
	 */
	return (RD_AP_EXP);
    }
#endif
    /* All seems OK */
    ad->reply.length = 0;

    return(RD_AP_OK);
}
