/*
 *
 *			   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
 *
 */

/* ESP processing as of the (obsolete) RFC1829/1851 (Metzger & Simpson) */

#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 CONFIG_IPSEC_ESP
#include "ipsec_esp.h"
#endif

extern void des_ecb3_encrypt(caddr_t, caddr_t, caddr_t, caddr_t, caddr_t, int);
extern void des_ecb_encrypt(caddr_t, caddr_t, caddr_t, int);
extern void des_set_key(caddr_t, caddr_t);

static void des1_encrypt(void *, u_int8_t *);
static void des3_encrypt(void *, u_int8_t *);
static void des1_decrypt(void *, u_int8_t *);
static void des3_decrypt(void *, u_int8_t *);

struct esp_xform esp_old_xform[] = {
     { ALG_ENC_3DES, "Tripple DES (3DES)",
       ESP_3DES_BLKS, ESP_3DES_IVS,
       24, 24, 8 | 4,
       des3_encrypt,
       des3_decrypt
     },
     { ALG_ENC_DES, "Data Encryption Standard (DES)",
       ESP_DES_BLKS, ESP_DES_IVS,
       8, 8, 8 | 4,
       des1_encrypt,
       des1_decrypt
     },
};

#ifdef DEBUG_IPSEC_ESP
static void dmp(char *s, caddr_t bb, int len)
{
        int i;
        unsigned char *b = bb;

        if (debug_ah & DB_AH_DMP)
        {
                printk("at %s, len=%d:", s, len);
                for (i=0; i < len; i++)
                        printk(" %02x", *b++);
                printk("\n");
        }
}
#else
#define dmp(_x, _y, _z)
#endif

static void
des1_encrypt(void *pxd, u_int8_t *blk)
{
     struct esp_old_xdata *xd = pxd;
     des_ecb_encrypt(blk, blk, (caddr_t) (xd->edx_eks[0]), 1);
}

static void
des1_decrypt(void *pxd, u_int8_t *blk)
{
     struct esp_old_xdata *xd = pxd;
     des_ecb_encrypt(blk, blk, (caddr_t) (xd->edx_eks[0]), 0);
}

static void
des3_encrypt(void *pxd, u_int8_t *blk)
{
     struct esp_old_xdata *xd = pxd;
     des_ecb3_encrypt(blk, blk, (caddr_t) (xd->edx_eks[2]),
                      (caddr_t) (xd->edx_eks[1]),
                      (caddr_t) (xd->edx_eks[0]), 1);
}

static void
des3_decrypt(void *pxd, u_int8_t *blk)
{
     struct esp_old_xdata *xd = pxd;
     des_ecb3_encrypt(blk, blk, (caddr_t) (xd->edx_eks[2]),
                      (caddr_t) (xd->edx_eks[1]),
                      (caddr_t) (xd->edx_eks[0]), 0);
}

#ifdef DEBUG_IPSEC_ESP
static void
print_ip_pkt(struct iphdr *ip)
{
	int i, len;
	unsigned char *d = (unsigned char *)ip;
	len = ntohs(ip->tot_len);
	
	printk("packet length: %d", len);
	for (i = 0; i < len; i++)
		printk(" %02x", d[i]);
	printk("\n");
}
#endif

int
esp_old_attach(void)
{
	printk("esp_old_attach: called.\n");
	return 0;
}

/*
 * esp_old_init() is called when an SPI is being set up. It interprets the
 * encap_msghdr present in em, and sets up the transformation data, in
 * this case, the encryption and decryption key schedules
 */

int
esp_old_init(struct tdb *tdbp, struct xformsw *xsp, struct encap_msghdr *em)
{
	struct esp_old_xdata *xd;
	struct esp_old_xencap xenc;
	struct esp_xform *txform;
	__u32 rk[6];
	int i;

	if ((em->em_msglen - EMT_SETSPI_FLEN) <= ESP_OLD_XENCAP_LEN)
	{
		printk("esp_old_init(): initialization failed\n");
		return EINVAL;
	}
	
	/* Just copy the standard fields */
	memcpy(&xenc, em->em_dat, ESP_OLD_XENCAP_LEN);

	/* Check wheather the encryption algorithm is supported */
	for (i=sizeof(esp_old_xform)/sizeof(struct esp_xform)-1; i >= 0; i--)
		if (xenc.edx_enc_algorithm == esp_old_xform[i].type)
			break;

	if (i < 0)
	{
		printk("esp_old_init(): unsupported encryption algorithm %d specified\n", xenc.edx_enc_algorithm);
		return EINVAL;
	}


	txform = &esp_old_xform[i];

#ifdef	IPSEC_ESP_DEBUG
		printk("esp_old_init(): intialized TDB with enc algorithm %d: %s\n", xenc.edx_enc_algorithm, esp_old_xform[i].name);
#endif

	if (xenc.edx_ivlen + xenc.edx_keylen + EMT_SETSPI_FLEN + ESP_OLD_XENCAP_LEN != em->em_msglen)
	{
		printk("esp_old_init(): message length %d doesn't match expected length %d\n", em->em_msglen, xenc.edx_ivlen + xenc.edx_keylen + EMT_SETSPI_FLEN + ESP_OLD_XENCAP_LEN);
		return EINVAL;
	}

	/* Check the IV length */
	if (((xenc.edx_ivlen == 0) && !(txform->ivmask&1)) ||
	    ((xenc.edx_ivlen != 0) && (
			!(xenc.edx_ivlen & txform->ivmask) ||
			(xenc.edx_ivlen & (xenc.edx_ivlen-1)))))
	{
		printk("esp_old_init(): unsupported IV length %d\n",
			xenc.edx_ivlen);
		return EINVAL;
	}

	/* Check the key length */
	if (xenc.edx_keylen < txform->minkey || xenc.edx_keylen > txform->maxkey)
	{
		printk("esp_old_init(): bad key length %d\n",xenc.edx_keylen);
		return EINVAL;
	}

	tdbp->tdb_xdata = (caddr_t)kmalloc(sizeof (struct esp_old_xdata), GFP_ATOMIC);

	if (tdbp->tdb_xdata == NULL)
	{
	  printk("esp_old_init(): malloc failed for xdata");
	  return ENOBUFS;
	}

	memset(tdbp->tdb_xdata, 0, sizeof (struct esp_old_xdata));
	xd = (struct esp_old_xdata *)tdbp->tdb_xdata;

	/* Pointer to the transform */
	tdbp->tdb_xform = xsp;

	xd->edx_ivlen = xenc.edx_ivlen;
	xd->edx_xform = txform;
	xd->edx_enc_algorithm = xenc.edx_enc_algorithm;

	/* Pass name of enc algorithm for procfs */
	tdbp->tdb_confname = xd->edx_xform->name;

	/* Copy the IV */
	memcpy(xd->edx_iv, em->em_dat+ESP_OLD_XENCAP_LEN, xd->edx_ivlen);

	/* Copy the key material */
	memcpy(rk, em->em_dat+ESP_OLD_XENCAP_LEN + xd->edx_ivlen, xenc.edx_keylen);

	switch (xd->edx_enc_algorithm)
	{
		case ALG_ENC_DES:
			des_set_key((caddr_t)rk, (caddr_t)(xd->edx_eks[0]));
			break;

		case ALG_ENC_3DES:
			des_set_key((caddr_t)rk, (caddr_t)(xd->edx_eks[0]));
			des_set_key((caddr_t)(rk+2), (caddr_t)(xd->edx_eks[1]));
			des_set_key((caddr_t)(rk+4), (caddr_t)(xd->edx_eks[2]));
			break;
	}

	memset(rk, 0, sizeof(rk));			/* paranoid */
	memset(ipseczeroes, 0, IPSEC_ZEROES_SIZE);	/* paranoid */

	return 0;
}

/* Free the memory */
int
esp_old_zeroize(struct tdb *tdbp)
{
#ifdef	IPSEC_ESP_DEBUG
	printk("esp_old_zeorize(): freeing memory\n");
#endif

	if (tdbp->tdb_xdata)
	{
		kfree(tdbp->tdb_xdata);
		tdbp->tdb_xdata = NULL;
	}
	return 0;
}

int
esp_old_print(void *xdat, char *buf)
{
	int size;
	int i, ds;

	struct esp_old_xdata *xd = (struct esp_old_xdata *)xdat;
	size = sprintf(buf, "algorithm = %d (%s), ivlen = %d, iv =", xd->edx_enc_algorithm, xd->edx_xform->name, xd->edx_ivlen);
	buf += size;

	for (i = 0; i < xd->edx_ivlen; i++)
	{
		ds = sprintf(buf, " %02x", xd->edx_iv[i]);
		buf += ds;
		size += ds;
	}

#ifdef ESPPRINTKEYS
	ds = sprintf(buf, ", ks =");
	buf += ds;
	size += ds;
	
	for (i = 8; i < 384 + 8; i++)
	{
		ds = sprintf(buf, " %02x", xd->edx_iv[i]);
		buf += ds;
		size += ds;
	}
#endif

	return size;
}

int
esp_old_room(struct tdb *tp, int iphlen, int tlen, int *hr, int *tr)
{
	struct esp_old_xdata *xd = (struct esp_old_xdata *)(tp->tdb_xdata) ;
	int blks;

	blks = xd->edx_xform->blocksize;
	
	*hr += sizeof(__u32) + xd->edx_ivlen;	/* SPI + IV */
	*tr += ((blks - ((tlen + 2) % blks)) % blks) + 2; /* Tail room for padding */
	return 0;
}

struct sk_buff *
esp_old_input(struct sk_buff *skb, struct tdb *tdbp)
{
	struct iphdr *ipp;
	struct esp_old_xdata *xd;
	struct esp_old *espp;
	int iphlen, esphlen, proto, len, pad, ilen, blks, l, i;
	unsigned char *dat, *idat, *odat;
	unsigned char iv[ESP_3DES_IVS], niv[ESP_3DES_IVS], blk[ESP_3DES_BLKS];
	
	len = skb->len; dat = skb->data;

	ipp = (struct iphdr *)dat;
	iphlen = ipp->ihl << 2;
	espp = (struct esp_old *)(dat + iphlen);
	
	xd = (struct esp_old_xdata *)tdbp->tdb_xdata;
	blks = xd->edx_xform->blocksize;

	esphlen = sizeof(__u32) + xd->edx_ivlen;	/* spi + iv */

	idat = odat = dat + iphlen + esphlen;
	ilen = len - iphlen - esphlen;

	if (ilen & (blks-1))
	{
		printk("ipsec_esp_old: got packet with ilen = %d \n", ilen);
		return NULL;
	}
	
	/* Get the IV from the packet */
	iv[0] = espp->esp_iv[0];
	iv[1] = espp->esp_iv[1];
	iv[2] = espp->esp_iv[2];
	iv[3] = espp->esp_iv[3];
	if (xd->edx_ivlen == 4)		/* Half-IV */
	{
		iv[4] = ~iv[0];
		iv[5] = ~iv[1];
		iv[6] = ~iv[2];
		iv[7] = ~iv[3];
	}
	else
	{
		iv[4] = espp->esp_iv[4];
		iv[5] = espp->esp_iv[5];
		iv[6] = espp->esp_iv[6];
		iv[7] = espp->esp_iv[7];
	}

	/* Use the ECB cipher in CBC mode */
	i = 0;
	for (l = 0; l < ilen; l++)
	{
		blk[i] = niv[i] = idat[l];
		i++;

		if (i==blks)
		{
			xd->edx_xform->decrypt(xd, blk);

			for (i = 0; i < blks; i++)
			{
				*odat++ = blk[i] ^ iv[i];
				iv[i] = niv[i];
			}
	
			i = 0;
		}
	}

	proto = idat[ilen - 1];
	pad = idat[ilen - 2] + 2;
	
#ifdef DEBUG_IPSEC_ESP
	if (debug_esp & DB_ES_IPAD)
	  printk("esp_old_input: proto = %d, padding = %d\n", proto, pad);
#endif

	if (pad >= ilen)
	{
		printk("esp_old_input(): pad(%d) >= ilen(%d) - dropping packet\n", pad, ilen);
		return NULL;
	}

	/*
	 *	Discard the ESP header
	 */
	 
	ipp->tot_len = htons(ntohs(ipp->tot_len) - esphlen - pad);
	ipp->protocol = proto;

	memmove((void *)(skb->data + esphlen), (void *)skb->data, iphlen);

	skb_pull(skb, esphlen);
	skb_trim(skb, len - esphlen - pad);

	/*
	 *	Adjust pointers
	 */
	 
	
	len = skb->len; dat = skb->data;
	

	skb->h.iph=(struct iphdr *)skb->data;
	skb->ip_hdr=(struct iphdr *)skb->data;

	memset(skb->proto_priv, 0, sizeof(struct options));
	
	ipp = (struct iphdr *)dat;
	ipp->check = 0;
	ipp->check = ip_fast_csum((unsigned char *)dat, iphlen >> 2);

	return skb;
}

int esp_old_output(struct sk_buff *skb, struct tdb *tdbp)
{
	struct iphdr *ipp;
	struct esp_old_xdata *xd;
	struct esp_old *espp;
	int iphlen, hrm, trm, len, ilen, blks, i, l;
	unsigned char *dat, *idat, *odat;
	unsigned char iv[ESP_3DES_IVS], blk[ESP_3DES_BLKS];
	
	ipp = (struct iphdr *)(skb->data);
#ifdef DEBUG_IPSEC_ESP
	if (debug_esp & DB_ES_OINFO)
	{
		printk("esp_old_output: old header (headroom = %d, tailroom = %d\n", skb_headroom(skb), skb_tailroom(skb));
		print_ip_pkt(ipp);
	}

#endif	
	iphlen = ipp->ihl << 2;
	if (iphlen != sizeof (struct iphdr))
	{
		printk("esp_old_output: cannot process options yet\n"); /* XXX */
		return -1;
	}

	xd = (struct esp_old_xdata *)tdbp->tdb_xdata;
	blks = xd->edx_xform->blocksize;

	hrm = sizeof(__u32) + xd->edx_ivlen; /* Head room for SPI and IV */
	trm = ((blks - ((skb->len - iphlen + 2) % blks)) % blks) + 2; /* Tail room for padding */

	dat = skb_push(skb, hrm);
	ilen = skb->len;
	len = ilen + trm;
	
#ifdef DEBUG_IPSEC_ESP
	if (debug_esp & DB_ES_OINFO2)
		printk("before skb_trim: %d,%d (need: %d,%d)", skb_headroom(skb), skb_tailroom(skb), hrm, trm);
#endif
	skb_put(skb, trm);
#ifdef DEBUG_IPSEC_ESP
	if (debug_esp & DB_ES_OINFO2)
		printk(" after skb_trim: %d,%d\n", skb_headroom(skb), skb_tailroom(skb));
#endif	
	memmove((void *)dat, (void *)(dat + hrm), iphlen);
	espp = (struct esp_old *)(dat + iphlen);
	ipp = (struct iphdr *)dat;

	if (ntohs(ipp->frag_off) & IP_OFFSET)
	{
		printk("esp_old_output: attempted to encrypt fragment!\n");
		return -1;
	}

	espp->esp_spi = tdbp->tdb_spi;

	iv[0] = espp->esp_iv[0] = xd->edx_iv[0];
	iv[1] = espp->esp_iv[1] = xd->edx_iv[1];
	iv[2] = espp->esp_iv[2] = xd->edx_iv[2];
	iv[3] = espp->esp_iv[3] = xd->edx_iv[3];

	if (xd->edx_ivlen == 4)
	{
		iv[4] = ~iv[0];
		iv[5] = ~iv[1];
		iv[6] = ~iv[2];
		iv[7] = ~iv[3];
	}
	else
	{
		iv[4] = espp->esp_iv[4] = xd->edx_iv[4];
		iv[5] = espp->esp_iv[5] = xd->edx_iv[5];
		iv[6] = espp->esp_iv[6] = xd->edx_iv[6];
		iv[7] = espp->esp_iv[7] = xd->edx_iv[7];
	}
	
	dat[len - 1] = ipp->protocol;
	dat[len - 2] = trm - 2;

/* the remaining pad bytes are left as is, but should be random */
	
	ipp->protocol = IPPROTO_ESP;
	ipp->tot_len  = htons(len);

	idat = odat = dat + iphlen + hrm;
	ilen = len - iphlen - hrm;

#ifdef DEBUG_IPSEC_ESP
	if (debug_esp & DB_ES_OINFO)
		printk("encrypting %d bytes\n", ilen);
#endif	

	/* Use the ECB block cipher in CBC mode */
	i = 0;
	for (l = 0; l < ilen; l++)
	{
		blk[i] = *idat++ ^ iv[i];
		i++;

		if (i==blks)
		{
			xd->edx_xform->encrypt(xd, blk);
			for (i = 0; i < blks; i++)
			{
				*odat++ = blk[i];
				iv[i] = blk[i];
			}

			i = 0;
		}
	}

	/* Save the last encrypted block, to be used as the next IV */
	memcpy(&xd->edx_iv, blk, xd->edx_ivlen);

	skb->ip_hdr = skb->h.iph = (struct iphdr *) skb->data;
	ipp->check = 0;
	ipp->check = ip_fast_csum((unsigned char *)ipp, ipp->ihl);

#ifdef DEBUG_IPSEC_ESP
	if (debug_esp & DB_ES_OH)
	{
		printk("esp_old_output: new header (headroom = %d, tailroom = %d\n", skb_headroom(skb), skb_tailroom(skb));
		print_ip_pkt(ipp);
	}
#endif


	return 0;
}
