/*
 * lib/krb5/krb/compat_recv.c
 *
 * Copyright 1993 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.  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.
 * 
 *
 * convenience sendauth/recvauth functions, with compatibility with V4
 * recvauth.
 *
 * NOTE: linking in this function will pull in V4 kerberos routines.
 *
 * WARNING: In the V4-style arguments, the ticket and kdata arguments
 * have different types than the V4 recvauth; in V4, they were KTEXT
 * and AUTH_DAT *, respectively.  Here, they are KTEXT * and AUTH_DAT **
 * and they are allocated by recvauth if and only if we end up talking
 * to a V4 sendauth.
 */

#define NEED_SOCKETS
#include "k5-int.h"
#if !defined(_MSDOS) && !defined(_MACINTOSH)
#include <kerberosIV/krb.h>
#include <kerberosIV/krb4-proto.h>
#include "com_err.h"
#include <errno.h>

#include <stdio.h>
#include <string.h>

static int krb_v4_recvauth();

#define	KRB_V4_SENDAUTH_VERS	"AUTHV0.1" /* MUST be 8 chars long */
#define KRB_V5_SENDAUTH_VERS	"KRB5_SENDAUTH_V1.0"

#define KRB5_RECVAUTH_V4	4
#define KRB5_RECVAUTH_V5	5

krb5_error_code
krb5_compat_recvauth(context, auth_context,
	             /* IN */
		     fdp, appl_version, server, flags, keytab,
		     v4_options, v4_service, v4_instance, v4_faddr, v4_laddr,
		     v4_filename, 
		     /* OUT */
		     ticket,
		     auth_sys, v4_kdata, v4_schedule, v4_version)
    krb5_context context;
    krb5_auth_context  *auth_context;
	krb5_pointer	fdp;
	char	*appl_version;
	krb5_principal	server;
	krb5_int32	flags;
	krb5_keytab    	keytab;
	krb5_ticket  ** ticket;
        krb5_int32      *auth_sys;

	/*
	 * Version 4 arguments
	 */
	krb5_int32 v4_options;	 /* bit-pattern of options */
	char *v4_service;	 /* service expected */
	char *v4_instance;	 /* inst expected (may be filled in) */
	struct sockaddr_in *v4_faddr; /* foreign address */
	struct sockaddr_in *v4_laddr; /* local address */
	AUTH_DAT **v4_kdata;	 /* kerberos data (returned) */
	char *v4_filename;	 /* name of file with service keys */
	Key_schedule v4_schedule; /* key schedule (return) */
	char *v4_version;		 /* version string (filled in) */
{
	union verslen {
		krb5_int32	len;
		char		vers[4];
	} vers;
	char	*buf;
	int	len, length;
	krb5_int32	retval;
	int		fd = *( (int *) fdp);
#ifdef KRB5_KRB4_COMPAT
	KTEXT		v4_ticket;	 /* storage for client's ticket */
#endif
		
	if ((retval = krb5_net_read(context, fd, vers.vers, 4)) != 4)
		return((retval < 0) ? errno : ECONNABORTED);

#ifdef KRB5_KRB4_COMPAT
	if (!strncmp(vers.vers, KRB_V4_SENDAUTH_VERS, 4)) {
		/*
		 * We must be talking to a V4 sendauth; read in the
		 * rest of the version string and make sure.
		 */
		if ((retval = krb5_net_read(context, fd, vers.vers, 4)) != 4)
			return((retval < 0) ? errno : ECONNABORTED);
		
		if (strncmp(vers.vers, KRB_V4_SENDAUTH_VERS+4, 4))
			return KRB5_SENDAUTH_BADAUTHVERS;

		*auth_sys = KRB5_RECVAUTH_V4;

		*v4_kdata = (AUTH_DAT *) malloc( sizeof(AUTH_DAT) );
		v4_ticket = (KTEXT) malloc(sizeof(KTEXT_ST));

		retval = krb_v4_recvauth(v4_options, fd, v4_ticket,
					 v4_service, v4_instance, v4_faddr,
					 v4_laddr, *v4_kdata, v4_filename,
					 v4_schedule, v4_version);
		krb5_xfree(v4_ticket);
		/*
		 * XXX error code translation?
		 */
		switch (retval) {
		case RD_AP_OK:
		    return 0;
		case RD_AP_TIME:
		    return KRB5KRB_AP_ERR_SKEW;
		case RD_AP_EXP:
		    return KRB5KRB_AP_ERR_TKT_EXPIRED;
		case RD_AP_NYV:
		    return KRB5KRB_AP_ERR_TKT_NYV;
		case RD_AP_NOT_US:
		    return KRB5KRB_AP_ERR_NOT_US;
		case RD_AP_UNDEC:
		    return KRB5KRB_AP_ERR_BAD_INTEGRITY;
		case RD_AP_REPEAT:
		    return KRB5KRB_AP_ERR_REPEAT;
		case RD_AP_MSG_TYPE:
		    return KRB5KRB_AP_ERR_MSG_TYPE;
		case RD_AP_MODIFIED:
		    return KRB5KRB_AP_ERR_MODIFIED;
		case RD_AP_ORDER:
		    return KRB5KRB_AP_ERR_BADORDER;
		case RD_AP_BADD:
		    return KRB5KRB_AP_ERR_BADADDR;
		default:
		    return KRB5_SENDAUTH_BADRESPONSE;
		}
	}
#endif

	/*
	 * Assume that we're talking to a V5 recvauth; read in the
	 * the version string, and make sure it matches.
	 */
	
	len = (int) ntohl(vers.len);

	if (len < 0 || len > 255)
		return KRB5_SENDAUTH_BADAUTHVERS;

	buf = malloc(len);
	if (!buf)
		return ENOMEM;
	
	length = krb5_net_read(context, fd, buf, len);
	if (len != length) {
		krb5_xfree(buf);
		if (len < 0)
			return errno;
		else
			return ECONNABORTED;
	}

	if (strcmp(buf, KRB_V5_SENDAUTH_VERS)) {
		krb5_xfree(buf);
		return KRB5_SENDAUTH_BADAUTHVERS;
	}
	krb5_xfree(buf);

	*auth_sys = KRB5_RECVAUTH_V5;
	
	retval = krb5_recvauth(context, auth_context, fdp, appl_version, server,
			       flags | KRB5_RECVAUTH_SKIP_VERSION, 
			       keytab, ticket);

	return retval;
}


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

#ifdef KRB5_KRB4_COMPAT	
static int
krb_v4_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 cc, old_vers = 0;
    int rem;
    krb5_int32 tkt_len, priv_len;
    krb5_ui_4 cksum;
    u_char tmp_buf[MAX_KTXT_LEN+max(KRB_SENDAUTH_VLEN+1,21)];

    /* 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);
}
#endif
#endif
