/*
 *
 *			   IPSEC for Linux
 *		         Preliminary Release
 * 
 *	 Copyright (C) 1996, 1997, John Ioannidis <ji@hol.gr>
 * 
 * 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
 *
 */

#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 DEBUG_IPSEC_RADIJ
int debug_radij = 0;
#endif

/*
 * Interface between the IPSEC code and the radix (radij) tree code.
 */

/* XXX static */ struct radij_node_head *rnh = 0;
	
extern int maj_keylen;

int
ipsec_radijinit(void)
{
	maj_keylen = sizeof (struct sockaddr_encap);

	rj_init();
	
	if (rj_inithead((void **)&rnh, 16) == 0) /* 16 is bit offset of sen_type */
		return -1;
	return 0;
}

struct eroute *
ipsec_makeroute(struct sockaddr_encap *eaddr, struct sockaddr_encap *emask,
	       struct sockaddr_encap *gw)
{
	struct eroute *retrt;
	struct radij_node *rn;
	
	retrt = (struct eroute *)kmalloc(sizeof (struct eroute), GFP_ATOMIC);
	if (retrt == NULL)
		return NULL;
	memset((caddr_t)retrt, 0, sizeof (struct eroute));
	
	retrt->er_eaddr = *eaddr;
	retrt->er_emask = *emask;
	retrt->er_dst = *gw;
	
	rd_key((&(retrt->er_rjt))) = &(retrt->er_eaddr);
	
	rn = rj_addroute(&(retrt->er_eaddr), &(retrt->er_emask), 
			 rnh, retrt->er_rjt.rd_nodes);
	
	if (rn == NULL)
	{
		printk("ipsec_addroute: rj_addroute returns NULL\n");
		/* kfree(retrt); XXX -- should we? */
		return NULL;
	}
	
	return retrt;
}

struct eroute *
ipsec_findroute(struct sockaddr_encap *eaddr)
{
	struct radij_node *rn;
	
#ifdef DEBUG_IPSEC_RADIJ
	if (debug_radij & DB_RJ_FINDROUTE)
	  printk("ipsec_findroute: 0x%x->0x%x\n", (u_int)ntohl(eaddr->sen_ip_src.s_addr), (u_int)ntohl(eaddr->sen_ip_dst.s_addr));
#endif
	rn = rj_match((caddr_t)eaddr, rnh);
	return (struct eroute *)rn;
}
		
int
ipsec_delroute(struct sockaddr_encap *eaddr, struct sockaddr_encap *emask)
{
	struct radij_node *rn;

	rn = rj_delete((caddr_t)eaddr, (caddr_t)emask, rnh);
	if (rn)
	{
		kfree(rn);
	}
	return 0;
}

struct wsbuf
{
	char *wb;
	int wl;
};

static int
walker(struct radij_node *rn, void *w0)
{
	struct eroute *ro = (struct eroute *)rn;
	struct rjtentry *rd = (struct rjtentry *)rn;
	struct wsbuf *w = (struct wsbuf *)w0;
	
	struct sockaddr_encap *key, *mask;
	int size;
	
	u_long as, ad, ms, md, ea;
	
	if (rn == NULL)
	{
		return 120;
	}
	
	if (rn->rj_b >= 0)
	{
		return 0;
	}
	
	key = rd_key(rd);
	mask = rd_mask(rd);
	
	if ((key == 0) || (mask == 0))
	{
		return 0;
	}
	
	as = key->sen_ip_src.s_addr;
	ad = key->sen_ip_dst.s_addr;
	ms = mask->sen_ip_src.s_addr;
	md = mask->sen_ip_dst.s_addr;
	ea = ro->er_dst.sen_ipsp_dst.s_addr; 
	
	
	size = sprintf(w->wb, "(%d.%d.%d.%d:%u:%u/%d.%d.%d.%d:%u:%u -> %d.%d.%d.%d:%u/%d.%d.%d.%d:%u) => (%d.%d.%d.%d, 0x%08x, %d)\n",

			 O4(as), O3(as), O2(as), O1(as), 
			 key->sen_proto,
			 ntohs(key->sen_sport),
			 O4(ms), O3(ms), O2(ms), O1(ms), 
			 mask->sen_proto,
			 ntohs(mask->sen_sport),
			 O4(ad), O3(ad), O2(ad), O1(ad), 
			 ntohs(key->sen_dport),
			 O4(md), O3(md), O2(md), O1(md), 
			 ntohs(mask->sen_dport),
			 O4(ea), O3(ea), O2(ea), O1(ea),
			 (u_int)ntohl(ro->er_dst.sen_ipsp_spi), ro->er_dst.sen_ipsp_sproto);
	w->wl += size;
	w->wb += size;
	return 0;
}



int
ipsec_walk(char *buffer)
{
	struct wsbuf ww = {buffer, 0};
	rj_walktree(rnh, walker, &ww);
	return ww.wl;
}
