/*
 * $Source: /cvs/pismere/pismere/athena/auth/krb4/krbv4/krbdll/recvauth.c,v $
 * $Author: dalmeida $
 *
 * Copyright 1987, 1988 by the Massachusetts Institute of Technology.
 *
 * For copying and distribution information, please see the file
 * <mit-copyright.h>.
 */

#ifndef lint
static char rcsid_recvauth_c[] =
"$Header: /cvs/pismere/pismere/athena/auth/krb4/krbv4/krbdll/recvauth.c,v 1.1 1999/03/12 23:06:11 dalmeida Exp $";
#endif  lint

#include <mit_copy.h>

#ifdef WINDOWS
#include <windows.h>
#endif
#include <krb.h>
#include <sys\types.h>
#ifndef WINSOCK
#include <netinet\in.h>
#else
#include <winsock.h>
#endif /*WINSOCK*/

#ifdef PC
#include <io.h>
#endif

/* #include <syslog.h> */
#include <errno.h>
#include <stdio.h>
#include <string.h>


#define KRB_SENDAUTH_VERS       "AUTHV0.1" /* MUST be KRB_SENDAUTH_VLEN
                                              chars */

/*
 * If the protocol changes, you will need to change the version string
 * and make appropriate changes in krb_sendauth.c
 * be sure to support old versions of krb_sendauth!
 */

#ifdef OS2
#define read(d,buf,n) recv((d),(buf),(n),0)
#endif

/*
 * krb_recvauth() reads (and optionally responds to) a message sent
 * using krb_sendauth().  The "options" argument is a bit-field of
 * selected options (see "sendauth.c" for options description).
 * The only option relevant to krb_recvauth() is KOPT_DO_MUTUAL
 * (mutual authentication requested).  The "fd" argument supplies
 * a file descriptor to read from (and write to, if mutual authenti-
 * cation is requested).
 *
 * Part of the received message will be a Kerberos ticket sent by the
 * client; this is read into the "ticket" argument.  The "service" and
 * "instance" arguments supply the server's Kerberos name.  If the
 * "instance" argument is the string "*", it is treated as a wild card
 * and filled in during the krb_rd_req() call (see read_service_key()).
 *
 * The "faddr" and "laddr" give the sending (client) and receiving
 * (local server) network addresses.  ("laddr" may be left NULL unless
 * mutual authentication is requested, in which case it must be set.)
 *
 * The authentication information extracted from the message is returned
 * in "kdata".  The "filename" argument indicates the file where the
 * server's key can be found.  (It is passed on to krb_rd_req().)  If
 * left null, the default "/etc/srvtab" will be used.
 *
 * If mutual authentication is requested, the session key schedule must
 * be computed in order to reply; this schedule is returned in the
 * "schedule" argument.  A string containing the application version
 * number from the received message is returned in "version", which
 * should be large enough to hold a KRB_SENDAUTH_VLEN-character string.
 *
 * See krb_sendauth() for the format of the received client message.
 *
 * This routine supports another client format, for backward
 * compatibility, consisting of:
 *
 * Size                 Variable                Field
 * ----                 --------                -----
 *
 * string               tmp_buf, tkt_len        length of ticket, in
 *                                              ascii
 *
 * char                 ' ' (space char)        separator
 *
 * tkt_len              ticket->dat             the ticket
 *
 * This old-style version does not support mutual authentication.
 *
 * krb_recvauth() first reads the protocol version string from the
 * given file descriptor.  If it doesn't match the current protocol
 * version (KRB_SENDAUTH_VERS), the old-style format is assumed.  In
 * that case, the string of characters up to the first space is read
 * and interpreted as the ticket length, then the ticket is read.
 *
 * If the first string did match KRB_SENDAUTH_VERS, krb_recvauth()
 * next reads the application protocol version string.  Then the
 * ticket length and ticket itself are read.
 *
 * The ticket is decrypted and checked by the call to krb_rd_req().
 * If no mutual authentication is required, the result of the
 * krb_rd_req() call is retured by this routine.  If mutual authenti-
 * cation is required, a message in the following format is returned
 * on "fd":
 *
 * Size                 Variable                Field
 * ----                 --------                -----
 *
 * 4 bytes              tkt_len                 length of ticket or -1
 *                                              if error occurred
 *
 * priv_len             tmp_buf                 "private" message created
 *                                              by krb_mk_priv() which
 *                                              contains the incremented
 *                                              checksum sent by the client
 *                                              encrypted in the session
 *                                              key.  (This field is not
 *                                              present in case of error.)
 *
 * If all goes well, KSUCCESS is returned; otherwise KFAILURE or some
 * other error code is returned.
 */

#ifndef max
#define max(a,b) (((a) > (b)) ? (a) : (b))
#endif /* max */

int
krb_recvauth(options, fd, ticket, service, instance, faddr, laddr, kdata,
             filename, schedule, version)
long options;                    /* bit-pattern of options */
int fd;                          /* file descr. to read from */
KTEXT ticket;                    /* storage for client's ticket */
char *service;                   /* service expected */
char *instance;                  /* inst expected (may be filled in) */
struct sockaddr_in *faddr;       /* address of foreign host on fd */
struct sockaddr_in *laddr;       /* local address */
AUTH_DAT *kdata;                 /* kerberos data (returned) */
char *filename;                  /* name of file with service keys */
Key_schedule schedule;           /* key schedule (return) */
char *version;                   /* version string (filled in) */
{

    int i, cc, old_vers = 0;
    char krb_vers[KRB_SENDAUTH_VLEN + 1]; /* + 1 for the null terminator */
    char *cp;
    int rem;
    long tkt_len, priv_len;
    u_long cksum;
    u_char tmp_buf[MAX_KTXT_LEN+max(KRB_SENDAUTH_VLEN+1,21)];

    /* read the protocol version number */
    if (krb_net_read(fd, krb_vers, KRB_SENDAUTH_VLEN) !=
        KRB_SENDAUTH_VLEN)
            return(errno);
    krb_vers[KRB_SENDAUTH_VLEN] = '\0';

    /* check version string */
    if (strcmp(krb_vers,KRB_SENDAUTH_VERS)) {
        /* Assume the old version of sendkerberosdata: send ascii
           length, ' ', and ticket. */
        if (options & KOPT_DO_MUTUAL)
            return(KFAILURE);    /* XXX can't do old style with mutual auth */
        old_vers = 1;

        /* copy what we have read into tmp_buf */
        (void) bcopy(krb_vers, (char *) tmp_buf, KRB_SENDAUTH_VLEN);

        /* search for space, and make it a null */
        for (i = 0; i < KRB_SENDAUTH_VLEN; i++)
            if (tmp_buf[i]== ' ') {
                tmp_buf[i] = '\0';
                /* point cp to the beginning of the real ticket */
                cp = (char *) &tmp_buf[i+1];
                break;
            }

        if (i == KRB_SENDAUTH_VLEN)
            /* didn't find the space, keep reading to find it */
            for (; i<20; i++) {
                if (read(fd, (char *)&tmp_buf[i], 1) != 1) {
                    return(KFAILURE);
                }
                if (tmp_buf[i] == ' ') {
                    tmp_buf[i] = '\0';
                    /* point cp to the beginning of the real ticket */
                    cp = (char *) &tmp_buf[i+1];
                    break;
                }
            }

        tkt_len = (long) atoi((char *) tmp_buf);

        /* sanity check the length */
        if ((i==20)||(tkt_len<=0)||(tkt_len>MAX_KTXT_LEN))
            return(KFAILURE);

        if (i < KRB_SENDAUTH_VLEN) {
            /* since we already got the space, and part of the ticket,
               we read fewer bytes to get the rest of the ticket */
            if (krb_net_read(fd, (char *)(tmp_buf+KRB_SENDAUTH_VLEN),
                             (int) (tkt_len - KRB_SENDAUTH_VLEN + 1 + i))
                != (int)(tkt_len - KRB_SENDAUTH_VLEN + 1 + i))
                return(errno);
        } else {
            if (krb_net_read(fd, (char *)(tmp_buf+i), (int)tkt_len) !=
                (int) tkt_len)
                return(errno);
        }
        ticket->length = tkt_len;
        /* copy the ticket into the struct */
        (void) bcopy(cp, (char *) ticket->dat, ticket->length);

    } else {
        /* read the application version string */
        if (krb_net_read(fd, version, KRB_SENDAUTH_VLEN) !=
            KRB_SENDAUTH_VLEN)
            return(errno);
        version[KRB_SENDAUTH_VLEN] = '\0';

        /* get the length of the ticket */
        if (krb_net_read(fd, (char *)&tkt_len, sizeof(tkt_len)) !=
            sizeof(tkt_len))
            return(errno);

        /* sanity check */
        ticket->length = ntohl((unsigned long)tkt_len);
        if ((ticket->length <= 0) || (ticket->length > MAX_KTXT_LEN)) {
            if (options & KOPT_DO_MUTUAL) {
                rem = KFAILURE;
                goto mutual_fail;
            } else
                return(KFAILURE); /* XXX there may still be junk on the fd? */
        }

        /* read the ticket */
        if (krb_net_read(fd, (char *) ticket->dat, ticket->length)
            != ticket->length)
            return(errno);
    }
    /*
     * now have the ticket.  decrypt it to get the authenticated
     * data.
     */
    rem = krb_rd_req(ticket,service,instance,faddr->sin_addr.s_addr,
                     kdata,filename);

    if (old_vers) return(rem);   /* XXX can't do mutual with old client */

    /* if we are doing mutual auth, compose a response */
    if (options & KOPT_DO_MUTUAL) {
        if (rem != KSUCCESS)
            /* the krb_rd_req failed */
            goto mutual_fail;

        /* add one to the (formerly) sealed checksum, and re-seal it
           for return to the client */
        cksum = kdata->checksum + 1;
        cksum = htonl(cksum);
#ifndef NOENCRYPTION
        key_sched(kdata->session,schedule);
#endif /* !NOENCRYPTION */
        priv_len = krb_mk_priv((unsigned char *)&cksum,
                               tmp_buf,
                               (unsigned long) sizeof(cksum),
                               schedule,
                               kdata->session,
                               laddr,
                               faddr);
        if (priv_len < 0) {
            /* re-sealing failed; notify the client */
            rem = KFAILURE;      /* XXX */
mutual_fail:
            priv_len = -1;
            tkt_len = htonl((unsigned long) priv_len);
            /* a length of -1 is interpreted as an authentication
               failure by the client */
            if ((cc = krb_net_write(fd, (char *)&tkt_len, sizeof(tkt_len)))
                != sizeof(tkt_len))
                return(cc);
            return(rem);
        } else {
            /* re-sealing succeeded, send the private message */
            tkt_len = htonl((unsigned long)priv_len);
            if ((cc = krb_net_write(fd, (char *)&tkt_len, sizeof(tkt_len)))
                 != sizeof(tkt_len))
                return(cc);
            if ((cc = krb_net_write(fd, (char *)tmp_buf, (int) priv_len))
                != (int) priv_len)
                return(cc);
        }
    }
    return(rem);
}
