/*
 *
 *			   IPSEC for Linux
 *			 Preliminary Release
 * 
 *	 Copyright (C) 1996, 1997, John Ioannidis <ji@hol.gr>
 *
 * Changes by Angelos D. Keromytis and Niels Provos
 * ported from OpenBSD 2.2 by Petr Novak, <pn@i.cz>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 */

/*
 * $Id: ipsec_ah.c,v 0.4 1997/01/15 01:28:15 ji Rel $
 *
 * $Log: ipsec_ah.c,v $
 * Revision 0.4  1997/01/15 01:28:15  ji
 * No changes.
 *
 * Revision 0.3  1996/11/20 14:35:48  ji
 * Minor Cleanup.
 * Rationalized debugging code.
 *
 * Revision 0.2  1996/11/02 00:18:33  ji
 * First limited release.
 *
 *
 */

#define __NO_VERSION__

#include <linux/module.h>
#include <linux/config.h>
#include <asm/segment.h>
#include <asm/system.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/config.h>

#include <linux/socket.h>
#include <linux/sockios.h>
#include <linux/in.h>
#include <linux/inet.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/icmp.h>
#include <linux/udp.h>
#include <net/ip.h>
#include <net/protocol.h>
#include <net/route.h>
#include <net/tcp.h>
#include <net/udp.h>
#include <net/sock.h>
#include <net/icmp.h>

#include <net/checksum.h>

#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/miscdevice.h>

#include <linux/skbuff.h>
#include <linux/proc_fs.h>
#include <linux/stat.h>

#include <net/netlink.h>
#include <unistd.h>
#include "radij.h"
#include "ipsec_encap.h"
#include "ipsec_radij.h"
#include "ipsec_netlink.h"
#include "ipsec_xform.h"
#ifdef CONFIG_IPSEC_AH
#include "ipsec_ah.h"
#endif

#ifdef DEBUG_IPSEC_AH
int debug_ah = 0xffffffff;
#endif

int 
ah_rcv(struct sk_buff *skb, struct device *dev, struct options *opt, 
       __u32 daddr, unsigned short len, __u32 saddr,
       int redo, struct inet_protocol *protocol)
{
	struct iphdr *ipp;
	struct ah_old *ahp;
	int iphlen;
#ifdef DEBUG_IPSEC_AH
	int i;
#endif
	unsigned char *dat;
	struct tdb *tdbp;
	struct in_addr iadst;
#ifdef DEBUG_IPSEC_TUNNEL
	struct timeval tv;
	do_gettimeofday(&tv);
	/*	if (debug_tunnel & DB_TN_REVEC) */
		printk("ipsec_ah_rcv: ts=%02d.%04d proto-51 packet received\n", 
		       tv.tv_sec % 60, tv.tv_usec / 100);
#endif DEBUG_IPSEC_TUNNEL
	
	/* Don't unlink in the middle of a turnaround */
	MOD_INC_USE_COUNT;
	
	len = skb->len; dat = skb->data;
	
#ifdef DEBUG_IPSEC_AH
	if (debug_ah & DB_AH_PKTRX)
	{
		printk("ipsec_ah: old ip packet is %d bytes:", len);
		for (i = 0; i < len; i++)
			printk(" %02x", dat[i]);
		printk("\n");
	}
#endif

	ipp = (struct iphdr *)skb->data;
	iphlen = ipp->ihl << 2;
	ipp->check = 0;			/* we know the sum is good */
	ahp = (struct ah_old *)(skb->data + iphlen);

	/*
	 * Find tunnel control block and (indirectly) call the appropriate
	 * tranform routine. The resulting sk_buf is a valid
	 * IP packet ready to go through input processing.
	 */

	iadst.s_addr = ipp->daddr;
	tdbp = gettdb(ahp->ah_spi, iadst, IPPROTO_AH);
	if (tdbp == NULL)
	{
#ifdef DEBUG_IPSEC_AH
		if (debug_ah & DB_AH_TDB)
			printk("ah_input: no tdb for spi=%x\n", (u_int)ntohl(ahp->ah_spi));
#endif
		goto rcvleave;
	}

	if (tdbp->tdb_flags & TDBF_INVALID)
	{
#ifdef	DEBUG_IPSEC_AH
		if (debug_ah & DB_AH_TDB)
			printk("ah_input(): attempted to use invalid AH SA %lx, packet %x->%x\n", ntohl(ahp->ah_spi), ipp->saddr, ipp->daddr);
#endif
		goto rcvleave;
	}

	if (tdbp->tdb_xform == NULL)
	{
#ifdef DEBUG_IPSEC_AH
		if (debug_ah & DB_AH_XF)
			printk("ah_input: no xform for spi=%x\n", (u_int)ntohl(ahp->ah_spi));
#endif 
		goto rcvleave;
	}

	skb = (*(tdbp->tdb_xform->xf_input))(skb, tdbp);
	
	if (skb == NULL)
	{
#ifdef	DEBUG_IPSEC_AH
		if (debug_ah & DB_AH_XF)
			printk("ah_input(): authentication failed for AH packet from %x to %x, spi %08lx\n", ipp->saddr, ipp->daddr, ntohl(ahp->ah_spi));
#endif
		goto rcvleave;
	}

	skb->protocol = htons(ETH_P_IP);
	skb->ip_summed = 0;

	ipp = (struct iphdr *)skb->data;

	if (ipp->protocol == IPPROTO_IPIP)	/* IP-in-IP encapsulation */
	{
	    /* Encapsulating SPI */
	    if (tdbp->tdb_osrc.s_addr && tdbp->tdb_odst.s_addr)
	    {
/* OpenBSD has a test for match of outer and inner IP addresses */
		;
	    }
	    else	/* So we're paranoid */
	    {
#ifdef	DEBUG_IPSEC_AH
		if (debug_ah & DB_AH_XF)
			printk("ah_input(): AH-tunnel used when expecting AH-transport, SA %08x/%x\n", tdbp->tdb_spi, tdbp->tdb_dst.s_addr);
#endif
		goto rcvleave;
	    }
	}
			
	netif_rx(skb);
     rcvleave:
	MOD_DEC_USE_COUNT;
	return(0);
}


struct inet_protocol ah_protocol = {
	ah_rcv,				/* AH handler */
#if 0
	NULL,				/* Will be UDP fraglist handler */
#endif
	NULL,				/* TUNNEL error control */
	0,				/* next */
	IPPROTO_AH,			/* protocol ID */
	0,				/* copy */
	NULL,				/* data */
	"AH"				/* name */
};
